1. 程式人生 > >龍芯1c上實現基於linux的spi驅動經驗

龍芯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