spi_imx.c
来自「linux 内核源代码」· C语言 代码 · 共 1,763 行 · 第 1/4 页
C
1,763 行
/* SPI chip-select management */ if (chip_info->cs_control) chip->cs_control = chip_info->cs_control; else chip->cs_control = null_cs_control; /* Save controller_state */ spi_set_ctldata(spi, chip); /* Summary */ dev_dbg(&spi->dev, "setup succeded\n" " loopback enable = %s\n" " dma enable = %s\n" " insert /ss pulse = %s\n" " period wait = %d\n" " mode = %d\n" " bits per word = %d\n" " min speed = %d Hz\n" " rounded max speed = %d Hz\n", chip->test & SPI_TEST_LBC ? "Yes" : "No", chip->enable_dma ? "Yes" : "No", chip->control & SPI_CONTROL_SSCTL ? "Yes" : "No", chip->period & SPI_PERIOD_WAIT, spi->mode, spi->bits_per_word, spi_speed_hz(SPI_CONTROL_DATARATE_MIN), spi->max_speed_hz); return status;err_first_setup: kfree(chip); return status;}static void cleanup(struct spi_device *spi){ kfree(spi_get_ctldata(spi));}static int __init init_queue(struct driver_data *drv_data){ INIT_LIST_HEAD(&drv_data->queue); spin_lock_init(&drv_data->lock); drv_data->run = QUEUE_STOPPED; drv_data->busy = 0; tasklet_init(&drv_data->pump_transfers, pump_transfers, (unsigned long)drv_data); INIT_WORK(&drv_data->work, pump_messages); drv_data->workqueue = create_singlethread_workqueue( drv_data->master->dev.parent->bus_id); if (drv_data->workqueue == NULL) return -EBUSY; return 0;}static int start_queue(struct driver_data *drv_data){ unsigned long flags; spin_lock_irqsave(&drv_data->lock, flags); if (drv_data->run == QUEUE_RUNNING || drv_data->busy) { spin_unlock_irqrestore(&drv_data->lock, flags); return -EBUSY; } drv_data->run = QUEUE_RUNNING; drv_data->cur_msg = NULL; drv_data->cur_transfer = NULL; drv_data->cur_chip = NULL; spin_unlock_irqrestore(&drv_data->lock, flags); queue_work(drv_data->workqueue, &drv_data->work); return 0;}static int stop_queue(struct driver_data *drv_data){ unsigned long flags; unsigned limit = 500; int status = 0; spin_lock_irqsave(&drv_data->lock, flags); /* This is a bit lame, but is optimized for the common execution path. * A wait_queue on the drv_data->busy could be used, but then the common * execution path (pump_messages) would be required to call wake_up or * friends on every SPI message. Do this instead */ drv_data->run = QUEUE_STOPPED; while (!list_empty(&drv_data->queue) && drv_data->busy && limit--) { spin_unlock_irqrestore(&drv_data->lock, flags); msleep(10); spin_lock_irqsave(&drv_data->lock, flags); } if (!list_empty(&drv_data->queue) || drv_data->busy) status = -EBUSY; spin_unlock_irqrestore(&drv_data->lock, flags); return status;}static int destroy_queue(struct driver_data *drv_data){ int status; status = stop_queue(drv_data); if (status != 0) return status; if (drv_data->workqueue) destroy_workqueue(drv_data->workqueue); return 0;}static int __init spi_imx_probe(struct platform_device *pdev){ struct device *dev = &pdev->dev; struct spi_imx_master *platform_info; struct spi_master *master; struct driver_data *drv_data = NULL; struct resource *res; int irq, status = 0; platform_info = dev->platform_data; if (platform_info == NULL) { dev_err(&pdev->dev, "probe - no platform data supplied\n"); status = -ENODEV; goto err_no_pdata; } /* Allocate master with space for drv_data */ master = spi_alloc_master(dev, sizeof(struct driver_data)); if (!master) { dev_err(&pdev->dev, "probe - cannot alloc spi_master\n"); status = -ENOMEM; goto err_no_mem; } drv_data = spi_master_get_devdata(master); drv_data->master = master; drv_data->master_info = platform_info; drv_data->pdev = pdev; master->bus_num = pdev->id; master->num_chipselect = platform_info->num_chipselect; master->cleanup = cleanup; master->setup = setup; master->transfer = transfer; drv_data->dummy_dma_buf = SPI_DUMMY_u32; /* Find and map resources */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "probe - MEM resources not defined\n"); status = -ENODEV; goto err_no_iores; } drv_data->ioarea = request_mem_region(res->start, res->end - res->start + 1, pdev->name); if (drv_data->ioarea == NULL) { dev_err(&pdev->dev, "probe - cannot reserve region\n"); status = -ENXIO; goto err_no_iores; } drv_data->regs = ioremap(res->start, res->end - res->start + 1); if (drv_data->regs == NULL) { dev_err(&pdev->dev, "probe - cannot map IO\n"); status = -ENXIO; goto err_no_iomap; } drv_data->rd_data_phys = (dma_addr_t)res->start; /* Attach to IRQ */ irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "probe - IRQ resource not defined\n"); status = -ENODEV; goto err_no_irqres; } status = request_irq(irq, spi_int, IRQF_DISABLED, dev->bus_id, drv_data); if (status < 0) { dev_err(&pdev->dev, "probe - cannot get IRQ (%d)\n", status); goto err_no_irqres; } /* Setup DMA if requested */ drv_data->tx_channel = -1; drv_data->rx_channel = -1; if (platform_info->enable_dma) { /* Get rx DMA channel */ status = imx_dma_request_by_prio(&drv_data->rx_channel, "spi_imx_rx", DMA_PRIO_HIGH); if (status < 0) { dev_err(dev, "probe - problem (%d) requesting rx channel\n", status); goto err_no_rxdma; } else imx_dma_setup_handlers(drv_data->rx_channel, NULL, dma_err_handler, drv_data); /* Get tx DMA channel */ status = imx_dma_request_by_prio(&drv_data->tx_channel, "spi_imx_tx", DMA_PRIO_MEDIUM); if (status < 0) { dev_err(dev, "probe - problem (%d) requesting tx channel\n", status); imx_dma_free(drv_data->rx_channel); goto err_no_txdma; } else imx_dma_setup_handlers(drv_data->tx_channel, dma_tx_handler, dma_err_handler, drv_data); /* Set request source and burst length for allocated channels */ switch (drv_data->pdev->id) { case 1: /* Using SPI1 */ RSSR(drv_data->rx_channel) = DMA_REQ_SPI1_R; RSSR(drv_data->tx_channel) = DMA_REQ_SPI1_T; break; case 2: /* Using SPI2 */ RSSR(drv_data->rx_channel) = DMA_REQ_SPI2_R; RSSR(drv_data->tx_channel) = DMA_REQ_SPI2_T; break; default: dev_err(dev, "probe - bad SPI Id\n"); imx_dma_free(drv_data->rx_channel); imx_dma_free(drv_data->tx_channel); status = -ENODEV; goto err_no_devid; } BLR(drv_data->rx_channel) = SPI_DMA_BLR; BLR(drv_data->tx_channel) = SPI_DMA_BLR; } /* Load default SPI configuration */ writel(SPI_RESET_START, drv_data->regs + SPI_RESET); writel(0, drv_data->regs + SPI_RESET); writel(SPI_DEFAULT_CONTROL, drv_data->regs + SPI_CONTROL); /* Initial and start queue */ status = init_queue(drv_data); if (status != 0) { dev_err(&pdev->dev, "probe - problem initializing queue\n"); goto err_init_queue; } status = start_queue(drv_data); if (status != 0) { dev_err(&pdev->dev, "probe - problem starting queue\n"); goto err_start_queue; } /* Register with the SPI framework */ platform_set_drvdata(pdev, drv_data); status = spi_register_master(master); if (status != 0) { dev_err(&pdev->dev, "probe - problem registering spi master\n"); goto err_spi_register; } dev_dbg(dev, "probe succeded\n"); return 0;err_init_queue:err_start_queue:err_spi_register: destroy_queue(drv_data);err_no_rxdma:err_no_txdma:err_no_devid: free_irq(irq, drv_data);err_no_irqres: iounmap(drv_data->regs);err_no_iomap: release_resource(drv_data->ioarea); kfree(drv_data->ioarea);err_no_iores: spi_master_put(master);err_no_pdata:err_no_mem: return status;}static int __exit spi_imx_remove(struct platform_device *pdev){ struct driver_data *drv_data = platform_get_drvdata(pdev); int irq; int status = 0; if (!drv_data) return 0; tasklet_kill(&drv_data->pump_transfers); /* Remove the queue */ status = destroy_queue(drv_data); if (status != 0) { dev_err(&pdev->dev, "queue remove failed (%d)\n", status); return status; } /* Reset SPI */ writel(SPI_RESET_START, drv_data->regs + SPI_RESET); writel(0, drv_data->regs + SPI_RESET); /* Release DMA */ if (drv_data->master_info->enable_dma) { RSSR(drv_data->rx_channel) = 0; RSSR(drv_data->tx_channel) = 0; imx_dma_free(drv_data->tx_channel); imx_dma_free(drv_data->rx_channel); } /* Release IRQ */ irq = platform_get_irq(pdev, 0); if (irq >= 0) free_irq(irq, drv_data); /* Release map resources */ iounmap(drv_data->regs); release_resource(drv_data->ioarea); kfree(drv_data->ioarea); /* Disconnect from the SPI framework */ spi_unregister_master(drv_data->master); spi_master_put(drv_data->master); /* Prevent double remove */ platform_set_drvdata(pdev, NULL); dev_dbg(&pdev->dev, "remove succeded\n"); return 0;}static void spi_imx_shutdown(struct platform_device *pdev){ struct driver_data *drv_data = platform_get_drvdata(pdev); /* Reset SPI */ writel(SPI_RESET_START, drv_data->regs + SPI_RESET); writel(0, drv_data->regs + SPI_RESET); dev_dbg(&pdev->dev, "shutdown succeded\n");}#ifdef CONFIG_PMstatic int suspend_devices(struct device *dev, void *pm_message){ pm_message_t *state = pm_message; if (dev->power.power_state.event != state->event) { dev_warn(dev, "pm state does not match request\n"); return -1; } return 0;}static int spi_imx_suspend(struct platform_device *pdev, pm_message_t state){ struct driver_data *drv_data = platform_get_drvdata(pdev); int status = 0; status = stop_queue(drv_data); if (status != 0) { dev_warn(&pdev->dev, "suspend cannot stop queue\n"); return status; } dev_dbg(&pdev->dev, "suspended\n"); return 0;}static int spi_imx_resume(struct platform_device *pdev){ struct driver_data *drv_data = platform_get_drvdata(pdev); int status = 0; /* Start the queue running */ status = start_queue(drv_data); if (status != 0) dev_err(&pdev->dev, "problem starting queue (%d)\n", status); else dev_dbg(&pdev->dev, "resumed\n"); return status;}#else#define spi_imx_suspend NULL#define spi_imx_resume NULL#endif /* CONFIG_PM */static struct platform_driver driver = { .driver = { .name = "spi_imx", .bus = &platform_bus_type, .owner = THIS_MODULE, }, .remove = __exit_p(spi_imx_remove), .shutdown = spi_imx_shutdown, .suspend = spi_imx_suspend, .resume = spi_imx_resume,};static int __init spi_imx_init(void){ return platform_driver_probe(&driver, spi_imx_probe);}module_init(spi_imx_init);static void __exit spi_imx_exit(void){ platform_driver_unregister(&driver);}module_exit(spi_imx_exit);MODULE_AUTHOR("Andrea Paterniani, <a.paterniani@swapp-eng.it>");MODULE_DESCRIPTION("iMX SPI Controller Driver");MODULE_LICENSE("GPL");
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?