龍芯1c上實現基於linux的spi驅動經驗
本文主要分享如何在龍芯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