atmel_spi.c
来自「linux 内核源代码」· C语言 代码 · 共 791 行 · 第 1/2 页
C
791 行
udelay(1); cs_activate(as, msg->spi); } /* * Not done yet. Submit the next transfer. * * FIXME handle protocol options for xfer */ atmel_spi_next_xfer(master, msg); } } else { /* * Keep going, we still have data to send in * the current transfer. */ atmel_spi_next_xfer(master, msg); } } spin_unlock(&as->lock); return ret;}/* the spi->mode bits understood by this driver: */#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)static int atmel_spi_setup(struct spi_device *spi){ struct atmel_spi *as; u32 scbr, csr; unsigned int bits = spi->bits_per_word; unsigned long bus_hz, sck_hz; unsigned int npcs_pin; int ret; as = spi_master_get_devdata(spi->master); if (as->stopping) return -ESHUTDOWN; if (spi->chip_select > spi->master->num_chipselect) { dev_dbg(&spi->dev, "setup: invalid chipselect %u (%u defined)\n", spi->chip_select, spi->master->num_chipselect); return -EINVAL; } if (bits == 0) bits = 8; if (bits < 8 || bits > 16) { dev_dbg(&spi->dev, "setup: invalid bits_per_word %u (8 to 16)\n", bits); return -EINVAL; } if (spi->mode & ~MODEBITS) { dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n", spi->mode & ~MODEBITS); return -EINVAL; } /* see notes above re chipselect */ if (cpu_is_at91rm9200() && spi->chip_select == 0 && (spi->mode & SPI_CS_HIGH)) { dev_dbg(&spi->dev, "setup: can't be active-high\n"); return -EINVAL; } /* speed zero convention is used by some upper layers */ bus_hz = clk_get_rate(as->clk); if (spi->max_speed_hz) { /* assume div32/fdiv/mbz == 0 */ if (!as->new_1) bus_hz /= 2; scbr = ((bus_hz + spi->max_speed_hz - 1) / spi->max_speed_hz); if (scbr >= (1 << SPI_SCBR_SIZE)) { dev_dbg(&spi->dev, "setup: %d Hz too slow, scbr %u; min %ld Hz\n", spi->max_speed_hz, scbr, bus_hz/255); return -EINVAL; } } else scbr = 0xff; sck_hz = bus_hz / scbr; csr = SPI_BF(SCBR, scbr) | SPI_BF(BITS, bits - 8); if (spi->mode & SPI_CPOL) csr |= SPI_BIT(CPOL); if (!(spi->mode & SPI_CPHA)) csr |= SPI_BIT(NCPHA); /* TODO: DLYBS and DLYBCT */ csr |= SPI_BF(DLYBS, 10); csr |= SPI_BF(DLYBCT, 10); /* chipselect must have been muxed as GPIO (e.g. in board setup) */ npcs_pin = (unsigned int)spi->controller_data; if (!spi->controller_state) { ret = gpio_request(npcs_pin, spi->dev.bus_id); if (ret) return ret; spi->controller_state = (void *)npcs_pin; gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH)); } else { unsigned long flags; spin_lock_irqsave(&as->lock, flags); if (as->stay == spi) as->stay = NULL; cs_deactivate(as, spi); spin_unlock_irqrestore(&as->lock, flags); } dev_dbg(&spi->dev, "setup: %lu Hz bpw %u mode 0x%x -> csr%d %08x\n", sck_hz, bits, spi->mode, spi->chip_select, csr); spi_writel(as, CSR0 + 4 * spi->chip_select, csr); return 0;}static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg){ struct atmel_spi *as; struct spi_transfer *xfer; unsigned long flags; struct device *controller = spi->master->dev.parent; as = spi_master_get_devdata(spi->master); dev_dbg(controller, "new message %p submitted for %s\n", msg, spi->dev.bus_id); if (unlikely(list_empty(&msg->transfers) || !spi->max_speed_hz)) return -EINVAL; if (as->stopping) return -ESHUTDOWN; list_for_each_entry(xfer, &msg->transfers, transfer_list) { if (!(xfer->tx_buf || xfer->rx_buf)) { dev_dbg(&spi->dev, "missing rx or tx buf\n"); return -EINVAL; } /* FIXME implement these protocol options!! */ if (xfer->bits_per_word || xfer->speed_hz) { dev_dbg(&spi->dev, "no protocol options yet\n"); return -ENOPROTOOPT; } /* * DMA map early, for performance (empties dcache ASAP) and * better fault reporting. This is a DMA-only driver. * * NOTE that if dma_unmap_single() ever starts to do work on * platforms supported by this driver, we would need to clean * up mappings for previously-mapped transfers. */ if (!msg->is_dma_mapped) { if (atmel_spi_dma_map_xfer(as, xfer) < 0) return -ENOMEM; } }#ifdef VERBOSE list_for_each_entry(xfer, &msg->transfers, transfer_list) { dev_dbg(controller, " xfer %p: len %u tx %p/%08x rx %p/%08x\n", xfer, xfer->len, xfer->tx_buf, xfer->tx_dma, xfer->rx_buf, xfer->rx_dma); }#endif msg->status = -EINPROGRESS; msg->actual_length = 0; spin_lock_irqsave(&as->lock, flags); list_add_tail(&msg->queue, &as->queue); if (!as->current_transfer) atmel_spi_next_message(spi->master); spin_unlock_irqrestore(&as->lock, flags); return 0;}static void atmel_spi_cleanup(struct spi_device *spi){ struct atmel_spi *as = spi_master_get_devdata(spi->master); unsigned gpio = (unsigned) spi->controller_data; unsigned long flags; if (!spi->controller_state) return; spin_lock_irqsave(&as->lock, flags); if (as->stay == spi) { as->stay = NULL; cs_deactivate(as, spi); } spin_unlock_irqrestore(&as->lock, flags); gpio_free(gpio);}/*-------------------------------------------------------------------------*/static int __init atmel_spi_probe(struct platform_device *pdev){ struct resource *regs; int irq; struct clk *clk; int ret; struct spi_master *master; struct atmel_spi *as; regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!regs) return -ENXIO; irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; clk = clk_get(&pdev->dev, "spi_clk"); if (IS_ERR(clk)) return PTR_ERR(clk); /* setup spi core then atmel-specific driver state */ ret = -ENOMEM; master = spi_alloc_master(&pdev->dev, sizeof *as); if (!master) goto out_free; master->bus_num = pdev->id; master->num_chipselect = 4; master->setup = atmel_spi_setup; master->transfer = atmel_spi_transfer; master->cleanup = atmel_spi_cleanup; platform_set_drvdata(pdev, master); as = spi_master_get_devdata(master); /* * Scratch buffer is used for throwaway rx and tx data. * It's coherent to minimize dcache pollution. */ as->buffer = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE, &as->buffer_dma, GFP_KERNEL); if (!as->buffer) goto out_free; spin_lock_init(&as->lock); INIT_LIST_HEAD(&as->queue); as->pdev = pdev; as->regs = ioremap(regs->start, (regs->end - regs->start) + 1); if (!as->regs) goto out_free_buffer; as->irq = irq; as->clk = clk; if (!cpu_is_at91rm9200()) as->new_1 = 1; ret = request_irq(irq, atmel_spi_interrupt, 0, pdev->dev.bus_id, master); if (ret) goto out_unmap_regs; /* Initialize the hardware */ clk_enable(clk); spi_writel(as, CR, SPI_BIT(SWRST)); spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS)); spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS)); spi_writel(as, CR, SPI_BIT(SPIEN)); /* go! */ dev_info(&pdev->dev, "Atmel SPI Controller at 0x%08lx (irq %d)\n", (unsigned long)regs->start, irq); ret = spi_register_master(master); if (ret) goto out_reset_hw; return 0;out_reset_hw: spi_writel(as, CR, SPI_BIT(SWRST)); clk_disable(clk); free_irq(irq, master);out_unmap_regs: iounmap(as->regs);out_free_buffer: dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer, as->buffer_dma);out_free: clk_put(clk); spi_master_put(master); return ret;}static int __exit atmel_spi_remove(struct platform_device *pdev){ struct spi_master *master = platform_get_drvdata(pdev); struct atmel_spi *as = spi_master_get_devdata(master); struct spi_message *msg; /* reset the hardware and block queue progress */ spin_lock_irq(&as->lock); as->stopping = 1; spi_writel(as, CR, SPI_BIT(SWRST)); spi_readl(as, SR); spin_unlock_irq(&as->lock); /* Terminate remaining queued transfers */ list_for_each_entry(msg, &as->queue, queue) { /* REVISIT unmapping the dma is a NOP on ARM and AVR32 * but we shouldn't depend on that... */ msg->status = -ESHUTDOWN; msg->complete(msg->context); } dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer, as->buffer_dma); clk_disable(as->clk); clk_put(as->clk); free_irq(as->irq, master); iounmap(as->regs); spi_unregister_master(master); return 0;}#ifdef CONFIG_PMstatic int atmel_spi_suspend(struct platform_device *pdev, pm_message_t mesg){ struct spi_master *master = platform_get_drvdata(pdev); struct atmel_spi *as = spi_master_get_devdata(master); clk_disable(as->clk); return 0;}static int atmel_spi_resume(struct platform_device *pdev){ struct spi_master *master = platform_get_drvdata(pdev); struct atmel_spi *as = spi_master_get_devdata(master); clk_enable(as->clk); return 0;}#else#define atmel_spi_suspend NULL#define atmel_spi_resume NULL#endifstatic struct platform_driver atmel_spi_driver = { .driver = { .name = "atmel_spi", .owner = THIS_MODULE, }, .suspend = atmel_spi_suspend, .resume = atmel_spi_resume, .remove = __exit_p(atmel_spi_remove),};static int __init atmel_spi_init(void){ return platform_driver_probe(&atmel_spi_driver, atmel_spi_probe);}module_init(atmel_spi_init);static void __exit atmel_spi_exit(void){ platform_driver_unregister(&atmel_spi_driver);}module_exit(atmel_spi_exit);MODULE_DESCRIPTION("Atmel AT32/AT91 SPI Controller driver");MODULE_AUTHOR("Haavard Skinnemoen <hskinnemoen@atmel.com>");MODULE_LICENSE("GPL");
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?