您的位置:首页 > 运维架构 > Linux

龙芯1c上实现基于linux的spi驱动经验

2016-11-01 17:23 706 查看
本文主要分享如何在龙芯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[]”,加入类似

<span style="font-size:18px;"><strong><span style="color:#6633FF;"><span style="background-color: rgb(255, 255, 255);">#ifdef CONFIG_SPI_MCP3201
{
.modalias	= "mcp3201",
.bus_num 	= 0,
.chip_select	= SPI0_CS3,
.max_speed_hz	= 1000000,
},
#endif</span></span></strong></span>


就可以使用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 <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/sysfs.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/spi/spi.h>

#define DRVNAME "mcp3201"
#define REFERENCE	5000

struct 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_GPIO
static int spi0_gpios_cs[] =
{ 81, 82, 83, 84 };
#endif

static 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  龙芯1c SPI驱动