/**************************************************************************
*本文主要跟踪imx6 spi设备和驱动的注册过程。
*
* Tony Liu, 2016-4-13, Shenzhen
*************************************************************************/
kernel/arch/arm/mach-mx6/board-mx6q_sabresd.c
MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board")
/* Maintainer: Freescale Semiconductor, Inc. */
.boot_params = MX6_PHYS_OFFSET + 0x100,
.fixup = fixup_mxc_board,
.map_io = mx6_map_io,
.init_irq = mx6_init_irq,
.init_machine = mx6_sabresd_board_init,
.timer = &mx6_sabresd_timer, |
.reserve = mx6q_sabresd_reserve, |
MACHINE_END |
V
static void __init mx6_sabresd_board_init(void)
{
... ...
imx6q_add_ecspi(0, &mx6q_sabresd_spi_data);
imx6q_add_ecspi(1, &mx6q_sabresd_spi2_data);
spi_device_init(); --------------------------------------------+
... ... | |
} | |
V |
static const struct spi_imx_master mx6q_sabresd_spi_data __initconst = { |
.chipselect = mx6q_sabresd_spi_cs, --------+ |
.num_chipselect = ARRAY_SIZE(mx6q_sabresd_spi_cs), | |
}; | |
| |
static int mx6q_sabresd_spi_cs[] = { <--------+ |
SABRESD_ECSPI1_CS1, |
}; |
#define SABRESD_ECSPI1_CS1 IMX_GPIO_NR(3, 19) |
|
//注册平台设备 |
#define imx6q_add_ecspi(id, pdata) \ |
imx_add_spi_imx(&imx6q_ecspi_data[id], pdata) ----+ |
| |
struct platform_device *__init imx_add_spi_imx( <---+ |
const struct imx_spi_imx_data *data, |
const struct spi_imx_master *pdata) |
{ |
struct resource res[] = { |
{ |
.start = data->iobase, |
.end = data->iobase + data->iosize - 1, |
.flags = IORESOURCE_MEM, |
}, { |
.start = data->irq, |
.end = data->irq, |
.flags = IORESOURCE_IRQ, |
}, |
}; |
return imx_add_platform_device(data->devid, data->id, |
res, ARRAY_SIZE(res), pdata, sizeof(*pdata)); |
} |
|
static void spi_device_init(void) <------------------------+
{
spi_register_board_info(imx6_sabresd_spi_nor_device, -------------+
ARRAY_SIZE(imx6_sabresd_spi_nor_device)); |
} | |
V |
static struct spi_board_info imx6_sabresd_spi_nor_device[] __initdata = { |
#if 0 |
#if defined(CONFIG_MTD_M25P80) |
{ |
.modalias = "m25p80", |
.max_speed_hz = 20000000, /* max spi clock (SCK) speed in HZ */ |
.bus_num = 0, |
.chip_select = 0, |
.platform_data = &imx6_sabresd__spi_flash_data, |
}, |
#endif |
#endif |
{ |
.modalias = "ar1020-spi", |
.max_speed_hz = 50000, /* max spi clock (SCK) speed in HZ */ |
.bus_num = 1, |
.chip_select = 0, |
.mode = SPI_MODE_1, |
.irq = gpio_to_irq(SABRESD_AR1020_INT), |
|
//.platform_data = &imx6_sabresd__spi_flash_data, |
}, |
|
}; |
|
int __init |
spi_register_board_info(struct spi_board_info const *info, unsigned n) <--+
{
struct boardinfo *bi;
int i;
bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
if (!bi)
return -ENOMEM;
for (i = 0; i < n; i++, bi++, info++) {
struct spi_master *master;
memcpy(&bi->board_info, info, sizeof(*info));
mutex_lock(&board_lock);
//将所有的spi设备添加到链表中
list_add_tail(&bi->list, &board_list);
list_for_each_entry(master, &spi_master_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock);
}
return 0;
}
kernel/drivers/spi/spi_imx.c
static int __init spi_imx_init(void)
{
return platform_driver_register(&spi_imx_driver);
} |
V
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME, // "spi_imx"
.owner = THIS_MODULE,
},
.id_table = spi_imx_devtype,
.probe = spi_imx_probe, -------+
.remove = __devexit_p(spi_imx_remove), |
}; |
V
static int __devinit spi_imx_probe(struct platform_device *pdev)
{
struct spi_imx_master *mxc_platform_info;
struct spi_master *master;
struct spi_imx_data *spi_imx;
struct resource *res;
int i, ret;
mxc_platform_info = dev_get_platdata(&pdev->dev);
if (!mxc_platform_info) {
dev_err(&pdev->dev, "can't get the platform data\n");
return -EINVAL;
}
master = spi_alloc_master(&pdev->dev, sizeof(struct spi_imx_data));
if (!master)
return -ENOMEM;
platform_set_drvdata(pdev, master);
master->bus_num = pdev->id;
master->num_chipselect = mxc_platform_info->num_chipselect;
spi_imx = spi_master_get_devdata(master);
spi_imx->bitbang.master = spi_master_get(master); ---------------------+
spi_imx->chipselect = mxc_platform_info->chipselect; |
//控制spi的chipselect引脚 |
for (i = 0; i < master->num_chipselect; i++) { |
if (spi_imx->chipselect[i] < 0) |
continue; |
ret = gpio_request(spi_imx->chipselect[i], DRIVER_NAME); |
if (ret) { |
while (i > 0) { |
i--; |
if (spi_imx->chipselect[i] >= 0) |
gpio_free(spi_imx->chipselect[i]); |
} |
dev_err(&pdev->dev, "can't get cs gpios\n"); |
goto out_master_put; |
} |
} |
// spi 对应的操作函数 |
spi_imx->bitbang.chipselect = spi_imx_chipselect; |
spi_imx->bitbang.setup_transfer = spi_imx_setupxfer; |
spi_imx->bitbang.txrx_bufs = spi_imx_transfer; |
spi_imx->bitbang.master->setup = spi_imx_setup; |
spi_imx->bitbang.master->cleanup = spi_imx_cleanup; |
spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; |
|
init_completion(&spi_imx->xfer_done); |
|
spi_imx->devtype_data = |
spi_imx_devtype_data[pdev->id_entry->driver_data]; |
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
if (!res) { |
dev_err(&pdev->dev, "can't get platform resource\n"); |
ret = -ENOMEM; |
goto out_gpio_free; |
} |
|
if (!request_mem_region(res->start, resource_size(res), pdev->name)) { |
dev_err(&pdev->dev, "request_mem_region failed\n"); |
ret = -EBUSY; |
goto out_gpio_free; |
} |
|
spi_imx->base = ioremap(res->start, resource_size(res)); |
if (!spi_imx->base) { |
ret = -EINVAL; |
goto out_release_mem; |
} |
|
spi_imx->irq = platform_get_irq(pdev, 0); |
if (spi_imx->irq < 0) { |
ret = -EINVAL; |
goto out_iounmap; |
} |
|
ret = request_irq(spi_imx->irq, spi_imx_isr, 0, DRIVER_NAME, spi_imx); |
if (ret) { |
dev_err(&pdev->dev, "can't get irq%d: %d\n", spi_imx->irq, ret); |
goto out_iounmap; |
} |
|
spi_imx->clk = clk_get(&pdev->dev, NULL); |
if (IS_ERR(spi_imx->clk)) { |
dev_err(&pdev->dev, "unable to get clock\n"); |
ret = PTR_ERR(spi_imx->clk); |
goto out_free_irq; |
} |
|
clk_enable(spi_imx->clk); |
spi_imx->spi_clk = clk_get_rate(spi_imx->clk); |
|
spi_imx->devtype_data.reset(spi_imx); |
|
spi_imx->devtype_data.intctrl(spi_imx, 0); |
ret = spi_bitbang_start(&spi_imx->bitbang); |
if (ret) { |
dev_err(&pdev->dev, "bitbang start failed with %d\n", ret); |
goto out_clk_put; |
} |
clk_disable(spi_imx->clk); |
|
dev_info(&pdev->dev, "probed\n"); |
|
return ret; |
|
out_clk_put: |
clk_disable(spi_imx->clk); |
clk_put(spi_imx->clk); |
out_free_irq: |
free_irq(spi_imx->irq, spi_imx); |
out_iounmap: |
iounmap(spi_imx->base); |
out_release_mem: |
release_mem_region(res->start, resource_size(res)); |
out_gpio_free: |
for (i = 0; i < master->num_chipselect; i++) |
if (spi_imx->chipselect[i] >= 0) |
gpio_free(spi_imx->chipselect[i]); |
out_master_put: |
spi_master_put(master); |
kfree(master); |
platform_set_drvdata(pdev, NULL); |
return ret; |
} |
|
int spi_bitbang_start(struct spi_bitbang *bitbang) <-----------------+
{
int status;
if (!bitbang->master || !bitbang->chipselect)
return -EINVAL;
INIT_WORK(&bitbang->work, bitbang_work);
spin_lock_init(&bitbang->lock);
INIT_LIST_HEAD(&bitbang->queue);
if (!bitbang->master->mode_bits)
bitbang->master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;
if (!bitbang->master->transfer)
bitbang->master->transfer = spi_bitbang_transfer;
if (!bitbang->txrx_bufs) {
bitbang->use_dma = 0;
bitbang->txrx_bufs = spi_bitbang_bufs;
if (!bitbang->master->setup) {
if (!bitbang->setup_transfer)
bitbang->setup_transfer =
spi_bitbang_setup_transfer;
bitbang->master->setup = spi_bitbang_setup;
bitbang->master->cleanup = spi_bitbang_cleanup;
}
} else if (!bitbang->master->setup)
return -EINVAL;
if (bitbang->master->transfer == spi_bitbang_transfer &&
!bitbang->setup_transfer)
return -EINVAL;
/* this task is the only thing to touch the SPI bits */
bitbang->busy = 0;
bitbang->workqueue = create_singlethread_workqueue(
dev_name(bitbang->master->dev.parent));
if (bitbang->workqueue == NULL) {
status = -EBUSY;
goto err1;
}
/* driver may get busy before register() returns, especially
* if someone registered boardinfo for devices
*/
status = spi_register_master(bitbang->master); -----+
if (status < 0) |
goto err2; |
|
return status; |
|
err2: |
destroy_workqueue(bitbang->workqueue); |
err1: |
return status; |
} |
|
int spi_register_master(struct spi_master *master) <-----+
{
static atomic_t dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
struct device *dev = master->dev.parent;
struct boardinfo *bi;
int status = -ENODEV;
int dynamic = 0;
if (!dev)
return -ENODEV;
/* even if it's just one always-selected device, there must
* be at least one chipselect
*/
if (master->num_chipselect == 0)
return -EINVAL;
/* convention: dynamically assigned bus IDs count down from the max */
if (master->bus_num < 0) {
/* FIXME switch to an IDR based scheme, something like
* I2C now uses, so we can't run out of "dynamic" IDs
*/
master->bus_num = atomic_dec_return(&dyn_bus_id);
dynamic = 1;
}
spin_lock_init(&master->bus_lock_spinlock);
mutex_init(&master->bus_lock_mutex);
master->bus_lock_flag = 0;
/* register the device, then userspace will see it.
* registration fails if the bus ID is in use.
*/
//设置设备节点的设备名 /dev/spi0, /dev/spi1
dev_set_name(&master->dev, "spi%u", master->bus_num);
status = device_add(&master->dev);
if (status < 0)
goto done;
dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
dynamic ? " (dynamic)" : "");
mutex_lock(&board_lock);
list_add_tail(&master->list, &spi_master_list);
list_for_each_entry(bi, &board_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock);
status = 0;
/* Register devices from the device tree */
of_register_spi_devices(master);
done:
return status;
}