代碼目錄
- 核心層:
drivers/spi/spi.c 實現SPI核心的功能 - 總線層
drivers/spi/spi-rockchip.c - 設備層
所用的spi總線的設備驅動
drivers/media/spi/rk1608.c
SPI核心層代碼分析
1.SPI子系統註冊函數:spi_init
drivers/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;
if (IS_ENABLED(CONFIG_OF_DYNAMIC))
WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));
return 0;
}
spi 總線驅動主要結構體
對應的節點:/sys/bus/spi
struct bus_type spi_bus_type = {
.name = "spi", //總線的名字
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
spi平臺(總線層)對應的主要結構體
對應的節點:/sys/class/spi_master
static struct class spi_master_class = {
.name = "spi_master",
.owner = THIS_MODULE,
.dev_release = spi_master_release,
.dev_groups = spi_master_groups, //其節點下有對應控制接口
};
2.spi_match_device函數分析:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev); //通過device指針獲取對應的spi_device 指針
const struct spi_driver *sdrv = to_spi_driver(drv); //通過device_driver指針獲取對應的spi_driver 指針
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv)) //如果spi_driver的of_device_id存在,則通過設備節點的compatible來匹配
return 1;
/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0;
}
struct spi_device {
struct device dev; //device 設備
struct spi_master *master; //spi設備的更高層描述,每一個spi控制器就對應一個master,一個spi設備必須對應一個master,master下可以有多個spi設備。
u32 max_speed_hz;
u8 chip_select;
u8 bits_per_word; //如果傳輸是以字節爲單位的話就設置爲8,相應地,如果是以2個字節爲單位則設置爲16。
u16 mode;
#define SPI_CPHA 0x01 /* clock phase 時鐘極性*/
#define SPI_CPOL 0x02 /* clock polarity 時鐘相位*/
#define SPI_MODE_0 (0|0) /* (original MicroWire) */ //四種模式,上篇文章有描述
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires */
int irq;
void *controller_state; //一般設置爲null
void *controller_data; //數據
char modalias[SPI_NAME_SIZE]; //,一般來說設備與驅動能否匹配起來就要看它,注意,這裏只是說一般,因爲還有另外兩種匹配的方法,不過大部分設備和驅動能否匹配起來都是根據名字是否相等,當然像USB驅動就不是根據名字來匹配的,而是根據與id table。
int cs_gpio; /* chip select gpio */
/* the statistics */
struct spi_statistics statistics;
/*
* likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
* - memory packing (12 bit samples into low bits, others zeroed)
* - priority
* - drop chipselect after each word
* - chipselect delays
* - ...
*/
};
對於具體的平臺,nand、iic這些都是平臺設備,spi當然也一樣是平臺設備,對於平臺設備,大部分情況下是先註冊設備再註冊驅動。
SPI總線驅動層代碼分析(drivers/spi/spi-rockchip.c)
spi1: spi@ff1d8000 {
compatible = "rockchip,px30-spi", "rockchip,rk3066-spi";
1.probe函數分析
static int rockchip_spi_probe(struct platform_device *pdev)
{
int ret = 0;
struct rockchip_spi *rs;
struct spi_master *master;
struct resource *mem;
u32 rsd_nsecs;
master = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi));
if (!master)
return -ENOMEM;
platform_set_drvdata(pdev, master);
rs = spi_master_get_devdata(master);
/* Get basic io resource and map it */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲取資源
rs->regs = devm_ioremap_resource(&pdev->dev, mem); //映射資源
if (IS_ERR(rs->regs)) {
ret = PTR_ERR(rs->regs);
goto err_ioremap_resource;
}
rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk");
if (IS_ERR(rs->apb_pclk)) {
dev_err(&pdev->dev, "Failed to get apb_pclk\n");
ret = PTR_ERR(rs->apb_pclk);
goto err_ioremap_resource;
}
rs->spiclk = devm_clk_get(&pdev->dev, "spiclk");
if (IS_ERR(rs->spiclk)) {
dev_err(&pdev->dev, "Failed to get spi_pclk\n");
ret = PTR_ERR(rs->spiclk);
goto err_ioremap_resource;
}
ret = clk_prepare_enable(rs->apb_pclk); //使能外圍時鐘("apb_pclk" for the peripheral clock.)
if (ret) {
dev_err(&pdev->dev, "Failed to enable apb_pclk\n");
goto err_ioremap_resource;
}
ret = clk_prepare_enable(rs->spiclk);//使能輸出時鐘("spiclk" for the transfer-clock)
if (ret) {
dev_err(&pdev->dev, "Failed to enable spi_clk\n");
goto err_spiclk_enable;
}
spi_enable_chip(rs, 0);
rs->type = SSI_MOTO_SPI;
rs->master = master;
rs->dev = &pdev->dev;
rs->max_freq = clk_get_rate(rs->spiclk);
if (!of_property_read_u32(pdev->dev.of_node, "rx-sample-delay-ns",
&rsd_nsecs))
rs->rsd_nsecs = rsd_nsecs;
rs->fifo_len = get_fifo_len(rs);
if (!rs->fifo_len) {
dev_err(&pdev->dev, "Failed to get fifo length\n");
ret = -EINVAL;
goto err_get_fifo_len;
}
spin_lock_init(&rs->lock);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
master->auto_runtime_pm = true;
master->bus_num = pdev->id;
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_LSB_FIRST; //設置spi模式 時鐘低電平時有效, 數據採樣發生在時鐘(SCK)偶數(2,4,6,...,16)邊沿
master->num_chipselect = 2;
master->dev.of_node = pdev->dev.of_node;
master->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8);
master->set_cs = rockchip_spi_set_cs;
master->prepare_message = rockchip_spi_prepare_message;
master->unprepare_message = rockchip_spi_unprepare_message;
master->transfer_one = rockchip_spi_transfer_one; //做slave的時候,需要做此函數設置
master->handle_err = rockchip_spi_handle_err;
rs->dma_tx.ch = dma_request_slave_channel(rs->dev, "tx");
if (IS_ERR_OR_NULL(rs->dma_tx.ch)) {
/* Check tx to see if we need defer probing driver */
if (PTR_ERR(rs->dma_tx.ch) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto err_get_fifo_len;
}
dev_warn(rs->dev, "Failed to request TX DMA channel\n");
}
rs->dma_rx.ch = dma_request_slave_channel(rs->dev, "rx");
if (!rs->dma_rx.ch) {
if (rs->dma_tx.ch) {
dma_release_channel(rs->dma_tx.ch);
rs->dma_tx.ch = NULL;
}
dev_warn(rs->dev, "Failed to request RX DMA channel\n");
}
if (rs->dma_tx.ch && rs->dma_rx.ch) {
rs->dma_tx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_TXDR);
rs->dma_rx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_RXDR);
rs->dma_tx.direction = DMA_MEM_TO_DEV;
rs->dma_rx.direction = DMA_DEV_TO_MEM;
master->can_dma = rockchip_spi_can_dma;
master->dma_tx = rs->dma_tx.ch;
master->dma_rx = rs->dma_rx.ch;
}
rs->high_speed_state = pinctrl_lookup_state(rs->dev->pins->p,
"high_speed");
if (IS_ERR_OR_NULL(rs->high_speed_state)) {
dev_warn(&pdev->dev, "no high_speed pinctrl state\n");
rs->high_speed_state = NULL;
}
ret = devm_spi_register_master(&pdev->dev, master);
if (ret) {
dev_err(&pdev->dev, "Failed to register master\n");
goto err_register_master;
}
return 0;
}
SPI設備驅動層代碼分析(drivers/media/spi/rk1608.c)
1.dtsi分析
&spi1 { 引用spi 控制器節點
status = "okay";
max-freq = <48000000>; spi內部工作時鐘
dma-names = "tx","rx"; 使能DMA模式,一般通訊字節少於32字節的不建議用
spi_rk1608@00 {
compatible = "rockchip,rk1608"; 與驅動對應的名字
reg = <0>; 片選0或者1
spi-max-frequency = <24000000>; spi clk輸出的時鐘頻率,不超過50M
spi-cpha; 如果有配,cpha爲1
spi-cpol; 如果有配,cpol爲1,clk腳保持高電平
status = "okay"; 使能設備節點
};
};
spi-max-frequency 是 SPI 的輸出時鐘,是 max-freq 分頻後輸出的,關係是 max-freq >= 2*spimax-frequency。
2.probe函數分析
static struct spi_driver rk1608_driver = {
.driver = {
.of_match_table = of_match_ptr(rk1608_of_match),
.name = "rk1608",
},
.probe = rk1608_probe,
.remove = rk1608_remove,
.id_table = rk1608_id,
};
static int rk1608_probe(struct spi_device *spi)
{
rk1608_parse_dt_property //解析dts數據
reset_gpio //高->低
irq_gpio //必須有
wakeup //可以沒有,he gpio pin to be used for waking up the controller
frequency //速率
解析dts 中rk1608的配置信息保存到struct rk1608_state *rk1608;
rk1608_get_remote_node_dev
rk1608_get_remote_node_dev
of_graph_get_remote_node //找到remote-endpoint節點信息保存到rk1608_state *rk1608
rk1608_initialize_controls //初始化v4l2_ctrl_handler
v4l2_spi_subdev_init
v4l2_subdev_init
devm_request_threaded_irq
rk1608_threaded_isr,
rk1608_msq_recv_msg // receive a msg from RK1608 -> AP msg queue 第一個參數msg queue,第二個參數a msg pointer buf.寫相應結存器,接受rk1608的信息.
rk1608_dev_register //調用到kernel/drivers/media/spi/rk1608_dev.c
pdata->misc.name = "rk_preisp";//名字data->misc.name = "rk_preisp",上層操作打開的節點
pdata->misc.fops = &rk1608_fops; //操作函數
static const struct file_operations rk1608_fops = {
.owner = THIS_MODULE,
.open = rk1608_dev_open,
*pdata =container_of(file->private_data, struct rk1608_state, misc);//pdata指向msic的首地址
rk1608_set_power(pdata, 1);
rk1608_power_on(pdata); //設置片選,復位,設置spi速度
rk1608_lsb_w32 //傳輸cmd ,addr,data.
rk1608_hw_init //硬件初始化
rk1608_download_fw //下載rk1608的固件,dts配置的固件名字
enable_irq //使能中斷
rk1608_set_log_level
rk1608_send_msg_to_dsp //send a msg to Soc->DSP msg queue
.release = rk1608_dev_release,
.write = rk1608_dev_write,
copy_from_user //從用戶空間copy
.poll = rk1608_dev_poll,
.unlocked_ioctl = rk1608_dev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = rk1608_compat_ioctl,
#endif
};
misc_register(&pdata->misc); //註冊字符設備
}