博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
龙芯1c上实现基于linux的spi驱动经验
阅读量:2360 次
发布时间:2019-05-10

本文共 11288 字,大约阅读时间需要 37 分钟。

本文主要分享如何在龙芯1c上实现linux下的spi驱动。

这里假设已经对spi有一定了解,不了解的自己百度。

使用硬件SPI

基础

百度上已经有很多关于linux下spi驱动的文章,讲得很好很全。比如:linux下spi驱动分为三层——SPI核心层、SPI控制器层、SPI设备驱动层。

其中SPI核心层是硬件无关的;SPI控制器层是SPI总线中master的驱动,是平台移植相关的,也就是龙芯1c上spi控制器的驱动,通常龙芯开发板里面已经实现了这部分;SPI设备驱动层是具体spi设备的驱动,通常只需要实现这部分就可以了。

说了这么多,到底怎样实现一个spi驱动。简单来说,只需要在platform.c中找到“static struct spi_board_info ls1x_spi0_devices[]”,加入类似

#ifdef CONFIG_SPI_MCP3201	{		.modalias	= "mcp3201",		.bus_num 	= 0,		.chip_select	= SPI0_CS3,		.max_speed_hz	= 1000000,	},#endif
就可以使用spi_read(),spi_write()和spi_write_then_read()来收发数据了。就这么简单

示例一:MCP3201驱动

上面的结构体中包含了spi设备的硬件接线情况,从其中可以知道设备mcp3201使用的是spi0的cs3,spi频率最大为1000000。下面来看看驱动源码

/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DRVNAME "mcp3201"#define REFERENCE 5000struct mcp3201 { struct device *hwmon_dev; struct mutex lock; u32 channels; u32 reference; /* in millivolts */ const char *name;};/* sysfs hook function */static ssize_t mcp3201_read(struct device *dev, struct device_attribute *devattr, char *buf, int differential){ struct spi_device *spi = to_spi_device(dev); struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct mcp3201 *adc = spi_get_drvdata(spi); u8 tx_buf[1]; u8 rx_buf[2]; int status = -1; u32 value = 0; if (mutex_lock_interruptible(&adc->lock)) return -ERESTARTSYS; switch (adc->channels) { case 1: /* mcp3201 */ status = spi_read(spi, rx_buf, sizeof(rx_buf)); break; case 2: /* mcp3202 */ if (differential) tx_buf[0] = 0x04 | attr->index; else tx_buf[0] = 0x06 | attr->index; status = spi_write_then_read(spi, tx_buf, sizeof(tx_buf), rx_buf, sizeof(rx_buf)); break; case 4: /* mcp3204 */ case 8: /* mcp3208 */ if (differential) tx_buf[0] = 0x10 | attr->index; else tx_buf[0] = 0x18 | attr->index; status = spi_write_then_read(spi, tx_buf, sizeof(tx_buf), rx_buf, sizeof(rx_buf)); break; } if (status < 0) { dev_warn(dev, "SPI synch. transfer failed with status %d\n", status); goto out; } switch (adc->channels) { case 1: /* mcp3201 */ value = (rx_buf[0] << 8); value = value & 0x1f00; value += rx_buf[1] ; value >>= 1; break; case 2: /* mcp3202 */ case 4: /* mcp3204 */ case 8: /* mcp3208 */ value = (rx_buf[0] & 0x3f) << 6 | (rx_buf[1] >> 2); break; } dev_dbg(dev, "raw value = 0x%x\n", value); value = value * adc->reference >> 12; status = sprintf(buf, "%d\n", value);out: mutex_unlock(&adc->lock); return status;}static ssize_t mcp3201_read_single(struct device *dev, struct device_attribute *devattr, char *buf){ return mcp3201_read(dev, devattr, buf, 0);}static ssize_t mcp3201_read_diff(struct device *dev, struct device_attribute *devattr, char *buf){ return mcp3201_read(dev, devattr, buf, 1);}static ssize_t mcp3201_show_min(struct device *dev, struct device_attribute *devattr, char *buf){ /* The minimum reference is 0 for this chip family */ return sprintf(buf, "0\n");}static ssize_t mcp3201_show_max(struct device *dev, struct device_attribute *devattr, char *buf){ struct spi_device *spi = to_spi_device(dev); struct mcp3201 *adc = spi_get_drvdata(spi); u32 reference; if (mutex_lock_interruptible(&adc->lock)) return -ERESTARTSYS; reference = adc->reference; mutex_unlock(&adc->lock); return sprintf(buf, "%d\n", reference);}static ssize_t mcp3201_set_max(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count){ struct spi_device *spi = to_spi_device(dev); struct mcp3201 *adc = spi_get_drvdata(spi); unsigned long value; if (strict_strtoul(buf, 10, &value)) return -EINVAL; if (mutex_lock_interruptible(&adc->lock)) return -ERESTARTSYS; adc->reference = value; mutex_unlock(&adc->lock); return count;}static ssize_t mcp3201_show_name(struct device *dev, struct device_attribute *devattr, char *buf){ struct spi_device *spi = to_spi_device(dev); struct mcp3201 *adc = spi_get_drvdata(spi); return sprintf(buf, "mcp320%d\n", adc->channels);}static struct sensor_device_attribute ad_input[] = { SENSOR_ATTR(name, S_IRUGO, mcp3201_show_name, NULL, 0), SENSOR_ATTR(Vin_min, S_IRUGO, mcp3201_show_min, NULL, 0), SENSOR_ATTR(Vin_max, S_IWUSR | S_IRUGO, mcp3201_show_max, mcp3201_set_max, 0), SENSOR_ATTR(single_ch0, S_IRUGO, mcp3201_read_single, NULL, 0), SENSOR_ATTR(diff_ch0+ch1-, S_IRUGO, mcp3201_read_diff, NULL, 0), SENSOR_ATTR(single_ch1, S_IRUGO, mcp3201_read_single, NULL, 1), SENSOR_ATTR(diff_ch1+ch0-, S_IRUGO, mcp3201_read_diff, NULL, 1), SENSOR_ATTR(single_ch2, S_IRUGO, mcp3201_read_single, NULL, 2), SENSOR_ATTR(diff_ch2+ch3-, S_IRUGO, mcp3201_read_diff, NULL, 2), SENSOR_ATTR(single_ch3, S_IRUGO, mcp3201_read_single, NULL, 3), SENSOR_ATTR(diff_ch3+ch2-, S_IRUGO, mcp3201_read_diff, NULL, 3), SENSOR_ATTR(single_ch4, S_IRUGO, mcp3201_read_single, NULL, 4), SENSOR_ATTR(diff_ch4+ch5-, S_IRUGO, mcp3201_read_diff, NULL, 4), SENSOR_ATTR(single_ch5, S_IRUGO, mcp3201_read_single, NULL, 5), SENSOR_ATTR(diff_ch5+ch4-, S_IRUGO, mcp3201_read_diff, NULL, 5), SENSOR_ATTR(single_ch6, S_IRUGO, mcp3201_read_single, NULL, 6), SENSOR_ATTR(diff_ch6+ch7-, S_IRUGO, mcp3201_read_diff, NULL, 6), SENSOR_ATTR(single_ch7, S_IRUGO, mcp3201_read_single, NULL, 7), SENSOR_ATTR(diff_ch7+ch6-, S_IRUGO, mcp3201_read_diff, NULL, 7),};/*----------------------------------------------------------------------*/static int __devinit mcp3201_probe(struct spi_device *spi){ int channels = spi_get_device_id(spi)->driver_data; struct mcp3201 *adc; int status; int i; adc = kzalloc(sizeof *adc, GFP_KERNEL); if (!adc) return -ENOMEM; /* set a default value for the reference */ adc->reference = REFERENCE; adc->channels = channels; adc->name = spi_get_device_id(spi)->name; mutex_init(&adc->lock); mutex_lock(&adc->lock); spi_set_drvdata(spi, adc); channels = 3 + (adc->channels << 1); for (i = 0; i < channels; i++) { status = device_create_file(&spi->dev, &ad_input[i].dev_attr); if (status) { dev_err(&spi->dev, "device_create_file failed.\n"); goto out_err; } } adc->hwmon_dev = hwmon_device_register(&spi->dev); if (IS_ERR(adc->hwmon_dev)) { dev_err(&spi->dev, "hwmon_device_register failed.\n"); status = PTR_ERR(adc->hwmon_dev); goto out_err; } mutex_unlock(&adc->lock); return 0;out_err: for (i--; i >= 0; i--) device_remove_file(&spi->dev, &ad_input[i].dev_attr); spi_set_drvdata(spi, NULL); mutex_unlock(&adc->lock); kfree(adc); return status;}static int __devexit mcp3201_remove(struct spi_device *spi){ int channels = spi_get_device_id(spi)->driver_data; struct mcp3201 *adc = spi_get_drvdata(spi); int i; mutex_lock(&adc->lock); hwmon_device_unregister(adc->hwmon_dev); channels = 3 + (adc->channels << 1); for (i = 0; i < channels; i++) device_remove_file(&spi->dev, &ad_input[i].dev_attr); spi_set_drvdata(spi, NULL); mutex_unlock(&adc->lock); kfree(adc); return 0;}static const struct spi_device_id mcp3201_ids[] = { { "mcp3201", 1 }, { "mcp3202", 2 }, { "mcp3204", 4 }, { "mcp3208", 8 }, { },};MODULE_DEVICE_TABLE(spi, mcp3201_ids);static struct spi_driver mcp3201_driver = { .driver = { .name = "mcp3201", .owner = THIS_MODULE, }, .id_table = mcp3201_ids, .probe = mcp3201_probe, .remove = __devexit_p(mcp3201_remove),};static int __init init_mcp3201(void){ return spi_register_driver(&mcp3201_driver);}static void __exit exit_mcp3201(void){ spi_unregister_driver(&mcp3201_driver);}module_init(init_mcp3201);module_exit(exit_mcp3201);MODULE_AUTHOR("loongson");MODULE_DESCRIPTION("mcp3201 Linux driver");MODULE_LICENSE("GPL");
这是1c的linux源码中的mcp3201.c的源码,说是1c的linux源码,其中也包括了1b的信息。这个mcp3201就是1b开发板上的设备,所以在1b-core的platform.c中就有mcp3201的信息。

mcp3201的驱动非常简单,涉及1c和mcp3201通信的函数只有mcp3201_read(),其它的都是套路。

linux的spi驱动中经常会用到spi_write_then_read(),这个函数的意思如名字——先写(命令)再读(数据)。比较典型的是AD芯片,先写需要读的数据是第几通道,然后读取数据。当然可以使用spi_write()然后再调spi_read()。两种的区别可以用示波器或逻辑分析仪看出来,简单描述就是spi_write_then_read()在的写和读是连续的,而用spi_write()和spi_read()组合出来的不连续。如下

上图为使用spi_write_then_read()的示波器的截图

上图为使用spi_write()和spi_read()组合的情况。

这两幅图是在调试TM7705时的示波器截图。先写入命令,再读16bit的数据。由于示波器只有两路,所以上图中只能看到SCLK和DOUT的数据,下图为SCK和DIN的数据

示例二:TM7705驱动

【龙印】龙芯1c上双路16位AD芯片TM7705的linux驱动

http://blog.csdn.net/caogos/article/details/53034196

进阶

在make menuconfig配置spi时,cs模式有两种,一种是gpio mode,另一种是softcs mode。如下

 gpio mode是指用自定义的gpio作为spi的cs脚,softcs mode是指使用系统默认的cs脚。platform.c中的代码如下

#ifdef CONFIG_SPI_CS_USED_GPIOstatic int spi0_gpios_cs[] =	{ 81, 82, 83, 84 };#endifstatic struct ls1x_spi_platform_data ls1x_spi0_platdata = {#ifdef CONFIG_SPI_CS_USED_GPIO	.gpio_cs_count = ARRAY_SIZE(spi0_gpios_cs),	.gpio_cs = spi0_gpios_cs,#elif CONFIG_SPI_CS	.cs_count = SPI0_CS3 + 1,#endif};
变量spi0_gpios_cs中定义的就是用作cs的4个gpio,龙芯1c有两个spi,这里是spi0,每个spi有4个片选。只需修改变量spi0_gpios_cs中对应的值。

源码“drivers\spi\spi_ls1x.c”中的函数ls1x_spi_chipselect()说得很清楚。

static void ls1x_spi_chipselect(struct spi_device *spi, int is_active){	struct ls1x_spi *hw = ls1x_spi_to_hw(spi);#ifdef CONFIG_SPI_CS_USED_GPIO	if (hw->gpio_cs_count) {		gpio_set_value(hw->gpio_cs[spi->chip_select],			(spi->mode & SPI_CS_HIGH) ? is_active : !is_active);	}#elif CONFIG_SPI_CS	u8 ret;	ret = readb(hw->base + REG_SOFTCS);	ret = (ret & 0xf0) | (0x01 << spi->chip_select);		if (unlikely(spi->mode & SPI_CS_HIGH)) {		if (is_active) {			ret = ret | (0x10 << spi->chip_select);			writeb(ret, hw->base + REG_SOFTCS);		} else {			ret = ret & (~(0x10 << spi->chip_select));			writeb(ret, hw->base + REG_SOFTCS);		}	} else {		if (is_active) {			ret = ret & (~(0x10 << spi->chip_select));			writeb(ret, hw->base + REG_SOFTCS);		} else {			ret = ret | (0x10 << spi->chip_select);			writeb(ret, hw->base + REG_SOFTCS);		}	}#endif}
除了片选脚可以改之外,还可以使用轮询模式和中断模式。这个就不用解释了。如下

使用GPIO模拟SPI

Linux内核已经写好了模拟SPI时序,你只需要配置好。就可以使用了

首先,你需要配置CONFIG。

config SPI_GPIO
tristate "GPIO-based bitbanging SPI Master"
depends on GENERIC_GPIO
select SPI_BITBANG
其次,你需要在你的平台注册platform_device,保证能让spi-gpio.c能执行到probe函数。

static struct spi_gpio_platform_data xxx_data = {          .sck = Pin(1),          .mosi = Pin(2),          .miso  = Pin(3),          .num_chipselect = 1,      };      struct platform_device xxx_device = {          .name       = DRIVER_NAME,          .id             = 0,          .dev = {              .platform_data = &xxx_data,          },      };
然后,你需要注册spi_board_info结构体,并初始化。

static struct spi_board_info xxxxx_board_info[] __initdata = {          {              .modalias   = xxxx,              .max_speed_hz   = 1200000,              .bus_num    = 0,              .chip_select    = 0,              .mode       = SPI_MODE_x,              .controller_data = (void *)Pin(4),           },      };
当你完成了以上步骤,恭喜你。模拟SPI已经配置成功了。接下来,你的硬件SPI驱动也可以兼容模拟IO的了。

模拟spi参考了《配置内核gpio模拟spi时序的方法》http://blog.csdn.net/liujun502589075/article/details/38798363

你可能感兴趣的文章
用javadoc命令生成api帮助文档
查看>>
MBR简介
查看>>
查看Linux内核版本号与发行版本号
查看>>
Ant中设置特定的jdk版本
查看>>
解决ant编译中出现“includeantruntime was not set”警告的问题
查看>>
用ant进行编译和打包
查看>>
解决Ubuntu 10.04更改主机名之后sudo报错的问题
查看>>
Ubuntu 10.04设置终端窗口的默认大小
查看>>
windowSoftInputMode属性详解
查看>>
Android Studio快捷键
查看>>
Java中final的用法总结
查看>>
四种获取Class对象的方法-Java反射机制
查看>>
eclipse用空格代替制表符
查看>>
Squid中文权威指南-第4章 快速配置向导
查看>>
Squid中文权威指南-第5章 运行Squid
查看>>
Squid中文权威指南-第6章 访问控制
查看>>
Squid中文权威指南-第7章 磁盘缓存基础
查看>>
Squid中文权威指南-第8章 高级磁盘缓存主题
查看>>
Squid中文权威指南-第9章 Cache拦截
查看>>
Squid中文权威指南-第10章 与其他Squid会话
查看>>