SPI匯流排驅動、裝置驅動
SPI驅動分析
zynq SPI控制器理解記錄
如下:spi一般都由一下兩個不通的模式組合成四個模式:
自動/手到傳送資料:
自動:當TxFIFO有資料就進行傳送;無資料則停止傳送。
手動:通過使能傳送位進行資料的傳送。
自動/手動控制CS使能訊號:
自動:當TXFIFO有資料的時候就自動使能,Txfifo資料傳輸完成之後就不使能
手動:通過使能位來軟體控制片選訊號的使能
SPI匯流排
一個SOC有多個spi控制器,這些控制器都是連線在SPI總線上,linux採用SPI匯流排來管理這些裝置。
其中driver/spi/spi.c中實現了對spi匯流排的註冊,同時為上層裝置提供了統一的介面,以下是spi.c中初始化程式碼
static int __init spi_init(void) { int status; buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); if (!buf) { status = -ENOMEM; goto err0; } status = bus_register(&spi_bus_type); if (status < 0) goto err1; status = class_register(&spi_master_class); if (status < 0) goto err2; return 0; err2: bus_unregister(&spi_bus_type); err1: kfree(buf); buf = NULL; err0: return status; } /* board_info is normally registered in arch_initcall(), * but even essential drivers wait till later * * REVISIT only boardinfo really needs static linking. the rest (device and * driver registration) _could_ be dynamically linked (modular) ... costs * include needing to have boardinfo data structures be much more public. */ postcore_initcall(spi_init);
其中postcore_initcall(*)是一個巨集,核心起來的時候會執行巨集裡面定義的函式。
status = bus_register(&spi_bus_type);實現對SPI匯流排的註冊
SPI Master控制器驅動
SPI Master控制器是SOC內部的控制器,一般一個SOC有多個控制器,使用同一個控制器驅動,對應的程式碼在drivers/spi/spi-xxx.c。每一個檔案對應著不通的spi控制器驅動,本文以spi-candance.c為例
/** * cdns_spi_probe - Probe method for the SPI driver * @pdev: Pointer to the platform_device structure * * This function initializes the driver data structures and the hardware. * * Return: 0 on success and error value on error */ static int cdns_spi_probe(struct platform_device *pdev) { int ret = 0, irq; struct spi_master *master; struct cdns_spi *xspi; struct resource *res; u32 num_cs; //pr_info("cdns_spi_probe....\r\n"); //pr_info("sizeof(*xspi)=%d\n", sizeof(*xspi)); master = spi_alloc_master(&pdev->dev, sizeof(*xspi)); if (master == NULL) return -ENOMEM; xspi = spi_master_get_devdata(master); master->dev.of_node = pdev->dev.of_node; platform_set_drvdata(pdev, master); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //pr_info("addr:0x%x\n", res->start); xspi->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(xspi->regs)) { ret = PTR_ERR(xspi->regs); goto remove_master; } xspi->pclk = devm_clk_get(&pdev->dev, "pclk"); if (IS_ERR(xspi->pclk)) { dev_err(&pdev->dev, "pclk clock not found.\n"); ret = PTR_ERR(xspi->pclk); goto remove_master; } xspi->ref_clk = devm_clk_get(&pdev->dev, "ref_clk"); if (IS_ERR(xspi->ref_clk)) { dev_err(&pdev->dev, "ref_clk clock not found.\n"); ret = PTR_ERR(xspi->ref_clk); goto remove_master; } ret = clk_prepare_enable(xspi->pclk); if (ret) { dev_err(&pdev->dev, "Unable to enable APB clock.\n"); goto remove_master; } ret = clk_prepare_enable(xspi->ref_clk); if (ret) { dev_err(&pdev->dev, "Unable to enable device clock.\n"); goto clk_dis_apb; } /* SPI controller initializations */ cdns_spi_init_hw(xspi); irq = platform_get_irq(pdev, 0); if (irq <= 0) { ret = -ENXIO; dev_err(&pdev->dev, "irq number is invalid\n"); goto remove_master; } ret = devm_request_irq(&pdev->dev, irq, cdns_spi_irq, 0, pdev->name, master); if (ret != 0) { ret = -ENXIO; dev_err(&pdev->dev, "request_irq failed\n"); goto remove_master; } ret = of_property_read_u32(pdev->dev.of_node, "num-cs", &num_cs); //pr_info("num_cs = %d\n", num_cs); if (ret < 0) master->num_chipselect = CDNS_SPI_DEFAULT_NUM_CS; else master->num_chipselect = num_cs; ret = of_property_read_u32(pdev->dev.of_node, "is-decoded-cs", &xspi->is_decoded_cs); //pr_info("is-decoded-cs = %d\n", xspi->is_decoded_cs); if (ret < 0) xspi->is_decoded_cs = 0; master->prepare_transfer_hardware = cdns_prepare_transfer_hardware; master->prepare_message = cdns_prepare_message; master->transfer_one = cdns_transfer_one; master->unprepare_transfer_hardware = cdns_unprepare_transfer_hardware; master->set_cs = cdns_spi_chipselect; master->mode_bits = SPI_CPOL | SPI_CPHA; /* Set to default valid value */ master->max_speed_hz = clk_get_rate(xspi->ref_clk) / 4; xspi->speed_hz = master->max_speed_hz; master->bits_per_word_mask = SPI_BPW_MASK(8); ret = spi_register_master(master); if (ret) { dev_err(&pdev->dev, "spi_register_master failed\n"); goto clk_dis_all; } return ret; clk_dis_all: clk_disable_unprepare(xspi->ref_clk); clk_dis_apb: clk_disable_unprepare(xspi->pclk); remove_master: spi_master_put(master); return ret; }
驅動就是linux驅動框架的三部曲
①,建立一個spi_master結構體
②,硬體初始化
③,結構體設定(相關驅動函式的編寫,根據裝置數進行相應的測試)
④,註冊結構體
SPI Device驅動
把一個連線的外設當做一個通用的裝置,驅動中只搭建spi通訊的介面,不實現裝置其它的協議,這裡有通用的驅動。
spi針對應用功能程式的驅動介面實現在drivers/spi/spidev.c裡面實現,跟普通的字元裝置驅動一樣,實現裝置的生成註冊;裝置驅動函式open、write、read、open函式的實現;這些函式直接呼叫通用層spi.c裡的函式,所以本原始碼檔案也是通用的,不需要針對不同的控制器進行修改。
其他外設使用SPI通訊
其他外設在裝置樹編寫的時候作為spi控制器下的一個子裝置,在對應的驅動程式的時候呼叫spi.c提供的介面即可使用對應spi控制器進行控制。
static int ds1305_probe(struct spi_device *spi)
{
struct ds1305 *ds1305;
int status;
u8 addr, value;
struct ds1305_platform_data *pdata = dev_get_platdata(&spi->dev);
bool write_ctrl = false;
/* Sanity check board setup data. This may be hooked up
* in 3wire mode, but we don't care. Note that unless
* there's an inverter in place, this needs SPI_CS_HIGH!
*/
if ((spi->bits_per_word && spi->bits_per_word != 8)
|| (spi->max_speed_hz > 2000000)
|| !(spi->mode & SPI_CPHA))
return -EINVAL;
/* set up driver data */
ds1305 = devm_kzalloc(&spi->dev, sizeof(*ds1305), GFP_KERNEL);
if (!ds1305)
return -ENOMEM;
ds1305->spi = spi;
spi_set_drvdata(spi, ds1305);
/* read and cache control registers */
addr = DS1305_CONTROL;
status = spi_write_then_read(spi, &addr, sizeof(addr),
ds1305->ctrl, sizeof(ds1305->ctrl));
if (status < 0) {
dev_dbg(&spi->dev, "can't %s, %d\n",
"read", status);
return status;
}
dev_dbg(&spi->dev, "ctrl %s: %3ph\n", "read", ds1305->ctrl);
/* Sanity check register values ... partially compensating for the
* fact that SPI has no device handshake. A pullup on MISO would
* make these tests fail; but not all systems will have one. If
* some register is neither 0x00 nor 0xff, a chip is likely there.
*/
if ((ds1305->ctrl[0] & 0x38) != 0 || (ds1305->ctrl[1] & 0xfc) != 0) {
dev_dbg(&spi->dev, "RTC chip is not present\n");
return -ENODEV;
}
if (ds1305->ctrl[2] == 0)
dev_dbg(&spi->dev, "chip may not be present\n");
/* enable writes if needed ... if we were paranoid it would
* make sense to enable them only when absolutely necessary.
*/
if (ds1305->ctrl[0] & DS1305_WP) {
u8 buf[2];
ds1305->ctrl[0] &= ~DS1305_WP;
buf[0] = DS1305_WRITE | DS1305_CONTROL;
buf[1] = ds1305->ctrl[0];
status = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);
dev_dbg(&spi->dev, "clear WP --> %d\n", status);
if (status < 0)
return status;
}
/* on DS1305, maybe start oscillator; like most low power
* oscillators, it may take a second to stabilize
*/
if (ds1305->ctrl[0] & DS1305_nEOSC) {
ds1305->ctrl[0] &= ~DS1305_nEOSC;
write_ctrl = true;
dev_warn(&spi->dev, "SET TIME!\n");
}
/* ack any pending IRQs */
if (ds1305->ctrl[1]) {
ds1305->ctrl[1] = 0;
write_ctrl = true;
}
/* this may need one-time (re)init */
if (pdata) {
/* maybe enable trickle charge */
if (((ds1305->ctrl[2] & 0xf0) != DS1305_TRICKLE_MAGIC)) {
ds1305->ctrl[2] = DS1305_TRICKLE_MAGIC
| pdata->trickle;
write_ctrl = true;
}
/* on DS1306, configure 1 Hz signal */
if (pdata->is_ds1306) {
if (pdata->en_1hz) {
if (!(ds1305->ctrl[0] & DS1306_1HZ)) {
ds1305->ctrl[0] |= DS1306_1HZ;
write_ctrl = true;
}
} else {
if (ds1305->ctrl[0] & DS1306_1HZ) {
ds1305->ctrl[0] &= ~DS1306_1HZ;
write_ctrl = true;
}
}
}
}
if (write_ctrl) {
u8 buf[4];
buf[0] = DS1305_WRITE | DS1305_CONTROL;
buf[1] = ds1305->ctrl[0];
buf[2] = ds1305->ctrl[1];
buf[3] = ds1305->ctrl[2];
status = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);
if (status < 0) {
dev_dbg(&spi->dev, "can't %s, %d\n",
"write", status);
return status;
}
dev_dbg(&spi->dev, "ctrl %s: %3ph\n", "write", ds1305->ctrl);
}
/* see if non-Linux software set up AM/PM mode */
addr = DS1305_HOUR;
status = spi_write_then_read(spi, &addr, sizeof(addr),
&value, sizeof(value));
if (status < 0) {
dev_dbg(&spi->dev, "read HOUR --> %d\n", status);
return status;
}
ds1305->hr12 = (DS1305_HR_12 & value) != 0;
if (ds1305->hr12)
dev_dbg(&spi->dev, "AM/PM\n");
/* register RTC ... from here on, ds1305->ctrl needs locking */
ds1305->rtc = devm_rtc_device_register(&spi->dev, "ds1305",
&ds1305_ops, THIS_MODULE);
if (IS_ERR(ds1305->rtc)) {
status = PTR_ERR(ds1305->rtc);
dev_dbg(&spi->dev, "register rtc --> %d\n", status);
return status;
}
/* Maybe set up alarm IRQ; be ready to handle it triggering right
* away. NOTE that we don't share this. The signal is active low,
* and we can't ack it before a SPI message delay. We temporarily
* disable the IRQ until it's acked, which lets us work with more
* IRQ trigger modes (not all IRQ controllers can do falling edge).
*/
if (spi->irq) {
INIT_WORK(&ds1305->work, ds1305_work);
status = devm_request_irq(&spi->dev, spi->irq, ds1305_irq,
0, dev_name(&ds1305->rtc->dev), ds1305);
if (status < 0) {
dev_err(&spi->dev, "request_irq %d --> %d\n",
spi->irq, status);
} else {
device_set_wakeup_capable(&spi->dev, 1);
}
}
/* export NVRAM */
status = sysfs_create_bin_file(&spi->dev.kobj, &nvram);
if (status < 0) {
dev_err(&spi->dev, "register nvram --> %d\n", status);
}
return 0;
}
spi_set_drvdata(spi, ds1305); 把spi相關的驅動儲存到此裝置當中,之後的此裝置驅動程式就可以利用spi來進行資料通訊
直接採用spi_write_then_read()函式來實現spi的讀寫。
原始碼記錄
問: spi fifo只有一定的大小,應用程式一次傳遞了超過fifo的資料,怎麼處理
答: 每次只把fifo寫滿,然後開啟發送fifo閾值中斷,當fifo中的資料數量少於一定的值時中斷產生,當發起的傳輸還有資料沒有傳送時則繼續傳送,直到所有的資料傳送完成。
問: spi通用傳輸的片選訊號在那裡控制
答: spi.c裡面在一次傳輸開始之前會先進行片選,而片選的具體操作函式則在spi-xxx.c裡面實現
問題記錄
一次傳遞一定數量的時候出現了超時的現象
spi.c裡在開始一次傳輸之後設定一個等待值等待資料傳輸完成,這裡的
ms = xfer->len * 8 * 1000 / xfer->speed_hz;計算在一定值得時候是零,就會出現在這個為零的值得數量的時候出現超時的情況,可以吧 tolerance的值加大,從而使其不出現超時的情況
if (ret > 0) {
ret = 0;
ms = xfer->len * 8 * 1000 / xfer->speed_hz;
ms += 100; /* some tolerance */
ms = wait_for_completion_timeout(&master->xfer_completion,
msecs_to_jiffies(ms));
}
完整函式如下:
static int spi_transfer_one_message(struct spi_master *master,
struct spi_message *msg)
{
struct spi_transfer *xfer;
bool keep_cs = false;
int ret = 0;
int ms = 1;
//printk("%s,%d\r\n",__func__,__LINE__);
spi_set_cs(msg->spi, true);
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
trace_spi_transfer_start(msg, xfer);
reinit_completion(&master->xfer_completion);
ret = master->transfer_one(master, msg->spi, xfer);
if (ret < 0) {
dev_err(&msg->spi->dev,
"SPI transfer failed: %d\n", ret);
goto out;
}
if (ret > 0) {
ret = 0;
ms = xfer->len * 8 * 1000 / xfer->speed_hz;
ms += 100; /* some tolerance */
ms = wait_for_completion_timeout(&master->xfer_completion,
msecs_to_jiffies(ms));
}
if (ms == 0) {
dev_err(&msg->spi->dev, "SPI transfer timed out\n");
msg->status = -ETIMEDOUT;
}
trace_spi_transfer_stop(msg, xfer);
if (msg->status != -EINPROGRESS)
goto out;
if (xfer->delay_usecs)
udelay(xfer->delay_usecs);
if (xfer->cs_change) {
if (list_is_last(&xfer->transfer_list,
&msg->transfers)) {
keep_cs = true;
} else {
spi_set_cs(msg->spi, false);
udelay(10);
spi_set_cs(msg->spi, true);
}
}
msg->actual_length += xfer->len;
}
out:
if (ret != 0 || !keep_cs)
spi_set_cs(msg->spi, false);
if (msg->status == -EINPROGRESS)
msg->status = ret;
spi_finalize_current_message(master);
return ret;
}