#define MULTICORE_DEBUG 1 // Включает сообщения dev_dbg (их можно просмотреть с помощью dmesg) #define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern u_int32_t mips_hpt_frequency; struct mc_spi { struct platform_device *pdev; struct spi_device *cur_spidev; int stopping; void __iomem *mfbsp_regs; #ifdef CONFIG_MULTICORE_SPI_DMA void __iomem *dma_tx_regs; void __iomem *dma_rx_regs; void *dma_txbuf; void *dma_rxbuf; dma_addr_t dma_txbuf_phys; dma_addr_t dma_rxbuf_phys; struct completion dma_completion; int dma_len; #endif }; static inline u32 _R(struct mc_spi *spic, unsigned idx) { return __raw_readl(spic->mfbsp_regs + idx); } static inline void _W(struct mc_spi *spic, unsigned idx, u32 val) { __raw_writel(val, spic->mfbsp_regs + idx); } static void cs_activate(struct mc_spi *s, int cs_num, int cs_high) { if (cs_high) _W(s, RG_TCTR, _R(s, RG_TCTR) | F_SS(cs_num)); else _W(s, RG_TCTR, _R(s, RG_TCTR) & ~F_SS(cs_num)); } static void cs_deactivate(struct mc_spi *s, int cs_num, int cs_high) { if (cs_high) _W(s, RG_TCTR, _R(s, RG_TCTR) & ~F_SS(cs_num)); else _W(s, RG_TCTR, _R(s, RG_TCTR) | F_SS(cs_num)); } static int hw_init_xfer(struct mc_spi *s, u8 bits, u32 speed) { // Максимальная частота, поддерживаемая контроллером - половина // частоты процессора unsigned long bus_hz = mips_hpt_frequency >> 1; // Делитель unsigned div; PDEBUGG("hw_init_xfer, port = %u, bits = %u, speed = %u\n", port, bits, speed); _W(s, RG_EMERG, F_RST_LPTBUF | F_RST_TXBUF | F_RST_RXBUF); _W(s, RG_RCTR, (_R(s, RG_RCTR) & ~F_RWORDLEN_MASK) | F_RWORDLEN(bits-1)); _W(s, RG_TCTR, (_R(s, RG_TCTR) & ~F_TWORDLEN_MASK) | F_TWORDLEN(bits-1)); if (speed) { div = DIV_ROUND_UP(bus_hz, speed) - 1; if (div > 0x3FF) { dev_dbg(&s->cur_spidev->dev, "setup: %d Hz too slow, divisor %u; min %ld Hz\n", speed, div, bus_hz/255); return -EINVAL; } } else // Нулевая скорость означает минимально возможную частоту div = 0x3FF; _W(s, RG_TCTR_RATE, F_TCLK_RATE(div)); _W(s, RG_RCTR_RATE, F_RCLK_RATE(div)); return 0; } #ifdef CONFIG_MULTICORE_SPI_DMA static void hw_start_dma(void __iomem *regs, dma_addr_t buf, int len) { __raw_writel(buf, regs + RG_DMA_IR); __raw_writel(F_DMA_WN(0) | F_DMA_WCX((len >> 3) - 1) | F_DMA_RUN, regs + RG_DMA_CSR); } static inline void hw_clear_interrupt(struct mc_spi *s) { (void)__raw_readl(s->dma_rx_regs + RG_DMA_CSR); } static int repack_to_dma(void *dma_buf, const void *src_buf, int len, u8 bits) { const u8 *pu8 = (u8 *) src_buf; const u16 *pu16 = (u16 *) src_buf; u32 *pdma = (u32 *) dma_buf; int dma_len; int i; PDEBUGG("repack_to_dma(%p, %p, %d, %u)\n", dma_buf, src_buf, len, bits); if (bits <= 8) { dma_len = (len << 2); if (pu8 == NULL) memset(pdma, 0, dma_len); else for (i = 0; i < len; ++i) { PDEBUGG("%02X\n", *pdma); *pdma++ = *pu8++; } } else if (bits > 8 && bits <= 16) { dma_len = (len << 1); if (pu16 == NULL) memset(pdma, 0, dma_len); else for (i = 0; i < ((len + 1) >> 1); ++i) *pdma++ = *pu16++; } else if (bits > 16 && bits <= 32) { dma_len = len; memcpy(dma_buf, src_buf, len); } else { return -EINVAL; } return dma_len; } static void repack_from_dma(const void *dma_buf, void *dst_buf, int len, u8 bits) { u8 *pu8 = dst_buf; u16 *pu16 = dst_buf; const u32 *pdma = dma_buf; int i; PDEBUGG("repack_from_dma(%p, %p, %d, %u)\n", dma_buf, dst_buf, len, bits); if (dst_buf == NULL) return; if (bits <= 8) { for (i = 0; i < len; ++i) { PDEBUGG("%02X\n", *pdma); *pu8++ = *pdma++; } } else if (bits > 8 && bits <= 16) { for (i = 0; i < ((len + 1) >> 1); ++i) *pu16++ = *pdma++; } else { memcpy(dst_buf, dma_buf, len); } } static irqreturn_t mc_spi_interrupt(int irq, void *dev_id) { struct spi_master *master = dev_id; struct mc_spi *spic = spi_master_get_devdata(master); PDEBUGG("%s\n", __func__); hw_clear_interrupt(spic); complete(&spic->dma_completion); return IRQ_HANDLED; } static int mc_spi_trx(struct mc_spi *s, struct spi_transfer *xfer, u8 bits) { u32 *pdma_buf; unsigned last; PDEBUGG("Enter %s, len = %d, bits = %d\n", __func__, xfer->len, bits); if (xfer->len > CONFIG_MULTICORE_SPI_DMA_BUFSZ) { dev_dbg(&s->cur_spidev->dev, "DMA buffers are too short: \ %d bytes, shall be at least: %d bytes \ (change configuration!)\n", CONFIG_MULTICORE_SPI_DMA_BUFSZ, xfer->len); return -EINVAL; } if ((xfer->len == 1 && bits <= 8) || (xfer->len == 2 && bits > 8 && bits <= 16) || ((xfer->len == 3 || xfer->len == 4) && bits > 16)) { PDEBUGG("tx_buf: %p, rx_buf: %p\n", xfer->tx_buf, xfer->rx_buf); if (xfer->tx_buf) if (bits <= 8) { _W(s, RG_TX, *((u8 *)xfer->tx_buf)); PDEBUGG("%02X\n", *((u8 *)xfer->tx_buf)); } else if (bits > 8 && bits <= 16) _W(s, RG_TX, *((u16 *)xfer->tx_buf)); else _W(s, RG_TX, *((u32 *)xfer->tx_buf)); else _W(s, RG_TX, 0); // Ожидаем завершения выдачи слова (и приёма ответного слова) while (_R(s, RG_RSR) & F_RBE); // Выбираем из приёмной очереди слово if (xfer->rx_buf) if (bits <= 8) *((u8 *)xfer->rx_buf) = _R(s, RG_RX); else if (bits > 8 && bits <= 16) *((u16 *)xfer->rx_buf) = _R(s, RG_RX); else *((u32 *)xfer->rx_buf) = _R(s, RG_RX); else _R(s, RG_RX); PDEBUGG("Exit %s\n", __func__); return 0; } s->dma_len = repack_to_dma(s->dma_txbuf, xfer->tx_buf, xfer->len, bits); if (s->dma_len < 0) { return -EINVAL; } PDEBUGG("dma_rx_regs: %p, dma_tx_regs: %p\n", s->dma_rx_regs, s->dma_tx_regs); hw_start_dma(s->dma_rx_regs, s->dma_rxbuf_phys, s->dma_len); hw_start_dma(s->dma_tx_regs, s->dma_txbuf_phys, s->dma_len); wait_for_completion(&s->dma_completion); PDEBUGG("wait_for_completion done\n"); if (s->dma_len & 4) { // Нечётное число слов в передаче - // последнее слово досылаем "вручную" pdma_buf = (u32 *) s->dma_txbuf; last = (s->dma_len >> 2) - 1; _W(s, RG_TX, pdma_buf[last]); // Ожидаем завершения выдачи слова (и приёма ответного слова) while (_R(s, RG_RSR) & F_RBE); // Выбираем из приёмной очереди слово pdma_buf = (u32 *) s->dma_rxbuf; pdma_buf[last] = _R(s, RG_RX); PDEBUGG("Manual txrx, len: %d, last_rx: %02x, last = %d\n", s->dma_len, pdma_buf[last], last); } repack_from_dma(s->dma_rxbuf, xfer->rx_buf, xfer->len, bits); PDEBUGG("Exit %s\n", __func__); return 0; } #else // !CONFIG_MULTICORE_SPI_DMA static int mc_spi_trx(struct mc_spi *s, struct spi_transfer *xfer, u8 bits) { u8 *rxp_8bit; u8 *txp_8bit; u16 *rxp_16bit; u16 *txp_16bit; u32 *rxp_32bit; u32 *txp_32bit; unsigned i, j; unsigned words; if (bits <= 8) { rxp_8bit = (u8 *) xfer->rx_buf; txp_8bit = (u8 *) xfer->tx_buf; i = 0; while (i < xfer->len) { while (!(_R(s, RG_TSR) & F_TBE)); for (j = 0; j < 16; ++j) { if (txp_8bit) { PDEBUGG("sent: %02x\n", *txp_8bit); _W(s, RG_TX, *txp_8bit); txp_8bit++; } else { PDEBUGG("sent: 00\n"); _W(s, RG_TX, 0); } ++i; if (i >= xfer->len) break; if (rxp_8bit) { while (!(_R(s, RG_RSR) & F_RBE)) { *rxp_8bit = _R(s, RG_RX); PDEBUGG("recv: %02x\n", *rxp_8bit); rxp_8bit++; } } } } while (!(_R(s, RG_TSR) & F_TSBE)); if (rxp_8bit) { while (rxp_8bit - (u8 *) xfer->rx_buf < xfer->len) { while (!(_R(s, RG_RSR) & F_RBE)) { *rxp_8bit = _R(s, RG_RX); PDEBUGG("recv: %02x\n", *rxp_8bit); rxp_8bit++; } } } } else if (bits > 8 && bits <= 16) { rxp_16bit = (u16 *) xfer->rx_buf; txp_16bit = (u16 *) xfer->tx_buf; i = 0; words = (xfer->len >> 1); while (i < words) { while (!(_R(s, RG_TSR) & F_TBE)); for (j = 0; j < 16; ++j) { if (txp_16bit) { PDEBUGG("sent: %04x\n", *txp_16bit); _W(s, RG_TX, *txp_16bit); txp_16bit++; } else { PDEBUGG("sent: 0000\n"); _W(s, RG_TX, 0); } ++i; if (i >= words) break; if (rxp_16bit) { while (!(_R(s, RG_RSR) & F_RBE)) { *rxp_16bit = _R(s, RG_RX); PDEBUGG("recv: %04x\n", *rxp_16bit); rxp_16bit++; } } } } while (!(_R(s, RG_TSR) & F_TSBE)); if (rxp_16bit) { while (rxp_16bit - (u16 *) xfer->rx_buf < words) { while (!(_R(s, RG_RSR) & F_RBE)) { *rxp_16bit = _R(s, RG_RX); PDEBUGG("recv: %04x\n", *rxp_16bit); rxp_16bit++; } } } } else if (bits > 16 && bits <= 32) { rxp_32bit = (u32 *) xfer->rx_buf; txp_32bit = (u32 *) xfer->tx_buf; i = 0; words = (xfer->len >> 2); while (i < words) { while (!(_R(s, RG_TSR) & F_TBE)); for (j = 0; j < 16; ++j) { if (txp_32bit) { PDEBUGG("sent: %08x\n", *txp_32bit); _W(s, RG_TX, *txp_32bit); txp_32bit++; } else { PDEBUGG("sent: 00000000\n"); _W(s, RG_TX, 0); } ++i; if (i >= words) break; if (rxp_32bit) { while (!(_R(s, RG_RSR) & F_RBE)) { *rxp_32bit = _R(s, RG_RX); PDEBUGG("recv: %08x\n", *rxp_32bit); rxp_32bit++; } } } } while (!(_R(s, RG_TSR) & F_TSBE)); if (rxp_32bit) { while (rxp_32bit - (u32 *) xfer->rx_buf < words) { while (!(_R(s, RG_RSR) & F_RBE)) { *rxp_32bit = _R(s, RG_RX); PDEBUGG("recv: %08x\n", *rxp_32bit); rxp_32bit++; } } } } else { dev_dbg(&s->cur_spidev->dev, "bad bits_per_word value: %u\n", bits); return -EINVAL; } return 0; } #endif // CONFIG_MULTICORE_SPI_DMA static int mc_spi_transfer_msg(struct spi_master *master, struct spi_message *msg) { struct mc_spi *spic; struct spi_transfer *xfer; struct spi_device *spidev = msg->spi; int port; u8 bits = spidev->bits_per_word; u32 speed = spidev->max_speed_hz; int ret; PDEBUGG("Enter %s\n", __func__); spic = spi_master_get_devdata(master); spic->cur_spidev = spidev; port = master->bus_num; PDEBUGG("new message %p submitted for %s, dma_mapped=%d\n", msg, dev_name(&spidev->dev), msg->is_dma_mapped); if (unlikely(list_empty(&msg->transfers))) { dev_dbg(&spidev->dev, "Message has no transfers!\n"); return -EINVAL; } if (unlikely(spic->stopping)) return -ESHUTDOWN; msg->actual_length = 0; msg->status = -EINPROGRESS; list_for_each_entry(xfer, &msg->transfers, transfer_list) { if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) { dev_dbg(&spidev->dev, "missing rx or tx buf\n"); return -EINVAL; } if (xfer->len == 0) { dev_dbg(&spidev->dev, "bad len of spi tranfer\n"); return -EINVAL; } PDEBUGG("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); if (xfer->bits_per_word != 0) bits = xfer->bits_per_word; if (xfer->speed_hz != 0) speed = xfer->speed_hz; cs_activate(spic, spidev->chip_select, spidev->mode & SPI_CS_HIGH); if (hw_init_xfer(spic, bits, speed) < 0) { msg->status = -EIO; return -EIO; } ret = mc_spi_trx(spic, xfer, bits); if (ret != 0) { msg->status = ret; return ret; } msg->actual_length += xfer->len; if (xfer->delay_usecs) udelay(xfer->delay_usecs); if (xfer->cs_change) { PDEBUGG("cs_change"); cs_deactivate(spic, spidev->chip_select, spidev->mode & SPI_CS_HIGH); udelay(1); cs_activate(spic, spidev->chip_select, spidev->mode & SPI_CS_HIGH); } } cs_deactivate(spic, spidev->chip_select, spidev->mode & SPI_CS_HIGH); msg->status = 0; spi_finalize_current_message(master); PDEBUGG("message done, msg @ %p, len: %d\n", msg, msg->actual_length); return 0; } static int mc_spi_setup(struct spi_device *spidev) { struct mc_spi *spic; u8 bits = spidev->bits_per_word; PDEBUGG("Enter %s\n", __func__); spic = spi_master_get_devdata(spidev->master); _W(spic, RG_RSTART, 0); _W(spic, RG_TSTART, 0); if (spic->stopping) return -ESHUTDOWN; if (bits < 2 || bits > 32) { dev_dbg(&spidev->dev, "SPI setup: invalid bits_per_word %u (2 to 32)\n", bits); return -EINVAL; } _W(spic, RG_RCTR, F_RMODE | F_RCLK_CP | F_RCS_CP | F_RMBF); _W(spic, RG_TCTR, F_TMODE | F_TMBF | F_SS_DO | F_SS(spidev->chip_select)); if (spidev->mode & SPI_CPOL) { _W(spic, RG_RCTR, _R(spic, RG_RCTR) | F_RNEG); _W(spic, RG_TCTR, _R(spic, RG_TCTR) | F_TNEG); } if (spidev->mode & SPI_CPHA) { _W(spic, RG_RCTR, _R(spic, RG_RCTR) | F_RDEL); _W(spic, RG_TCTR, _R(spic, RG_TCTR) | F_TDEL); } _W(spic, RG_RSTART, 1); _W(spic, RG_TSTART, 1); return 0; } static void mc_spi_cleanup(struct spi_device *spidev) { #ifdef CONFIG_MULTICORE_SPI_DMA int port = spidev->master->bus_num; disable_irq(mfbsp_dma_rx_irq_num(port)); free_irq(mfbsp_dma_rx_irq_num(port), spidev->master); #endif } /*--------------------------------------------------------------------*/ static __devinit int mc_spi_probe(struct platform_device *pdev) { int ret; struct spi_master *master; struct mc_spi *spic; struct resource *r; unsigned dir; ret = -ENOMEM; master = spi_alloc_master(&pdev->dev, sizeof(struct mc_spi)); if (!master) goto err_return; // Биты режима, которые понимает этот драйвер master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; master->dma_alignment = 4; master->bus_num = pdev->id; master->num_chipselect = 2; master->setup = mc_spi_setup; master->transfer_one_message = mc_spi_transfer_msg; master->cleanup = mc_spi_cleanup; platform_set_drvdata(pdev, master); spic = spi_master_get_devdata(master); spic->pdev = pdev; MC_CLKEN |= MC_CLKEN_MFBSP; r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); if (r == NULL) { ret = -ENODEV; goto err_get_res; } spic->mfbsp_regs = devm_request_and_ioremap(&pdev->dev, r); if (!spic->mfbsp_regs) { ret = -ENOMEM; goto err_get_res; } #ifdef CONFIG_MULTICORE_SPI_DMA r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "txdma"); if (r == NULL) { ret = -ENODEV; goto err_get_res; } spic->dma_tx_regs = devm_request_and_ioremap(&pdev->dev, r); if (!spic->dma_tx_regs) { ret = -ENOMEM; goto err_get_res; } r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rxdma"); if (r == NULL) { ret = -ENODEV; goto err_get_res; } spic->dma_rx_regs = devm_request_and_ioremap(&pdev->dev, r); if (!spic->dma_rx_regs) { ret = -ENOMEM; goto err_get_res; } spic->dma_txbuf = dmam_alloc_coherent(&pdev->dev, CONFIG_MULTICORE_SPI_DMA_BUFSZ, &spic->dma_txbuf_phys, GFP_KERNEL); if (!spic->dma_txbuf) { ret = -ENOMEM; goto err_get_res; } spic->dma_rxbuf = dmam_alloc_coherent(&pdev->dev, CONFIG_MULTICORE_SPI_DMA_BUFSZ, &spic->dma_rxbuf_phys, GFP_KERNEL); if (!spic->dma_rxbuf) { ret = -ENOMEM; goto err_get_res; } #endif ret = spi_register_master(master); if (ret) goto err_get_res; #ifdef CONFIG_MULTICORE_SPI_DMA init_completion(&spic->dma_completion); ret = request_irq (mfbsp_dma_rx_irq_num(master->bus_num), mc_spi_interrupt, 0, "spi_rx_dma", master); if (ret < 0) return ret; #endif _W(spic, RG_CSR, F_SPI_I2S_EN); switch(master->bus_num) { case 0: dir = F_RCLK_DIR | F_TCLK_DIR | F_TCS_DIR | F_RCS_DIR; #ifdef CONFIG_MULTICORE_SPI0_MOSI_OUT dir |= F_TD_DIR; #endif #ifdef CONFIG_MULTICORE_SPI0_MISO_OUT dir |= F_RD_DIR; #endif #ifdef CONFIG_MULTICORE_SPI0_SS0_OUT dir |= F_TCS_DIR; #endif #ifdef CONFIG_MULTICORE_SPI0_SS1_OUT dir |= F_RCS_DIR; #endif #ifdef CONFIG_MULTICORE_SPI0_TSCK_OUT dir |= F_TCLK_DIR; #endif #ifdef CONFIG_MULTICORE_SPI0_RSCK_OUT dir |= F_RCLK_DIR; #endif break; case 1: dir = F_RCLK_DIR | F_TCLK_DIR | F_TCS_DIR | F_RCS_DIR; #ifdef CONFIG_MULTICORE_SPI1_MOSI_OUT dir |= F_TD_DIR; #endif #ifdef CONFIG_MULTICORE_SPI1_MISO_OUT dir |= F_RD_DIR; #endif #ifdef CONFIG_MULTICORE_SPI1_SS0_OUT dir |= F_TCS_DIR; #endif #ifdef CONFIG_MULTICORE_SPI1_SS1_OUT dir |= F_RCS_DIR; #endif #ifdef CONFIG_MULTICORE_SPI1_TSCK_OUT dir |= F_TCLK_DIR; #endif #ifdef CONFIG_MULTICORE_SPI1_RSCK_OUT dir |= F_RCLK_DIR; #endif break; case 2: dir = F_RCLK_DIR | F_TCLK_DIR | F_TCS_DIR | F_RCS_DIR; #ifdef CONFIG_MULTICORE_SPI2_MOSI_OUT dir |= F_TD_DIR; #endif #ifdef CONFIG_MULTICORE_SPI2_MISO_OUT dir |= F_RD_DIR; #endif #ifdef CONFIG_MULTICORE_SPI2_SS0_OUT dir |= F_TCS_DIR; #endif #ifdef CONFIG_MULTICORE_SPI2_SS1_OUT dir |= F_RCS_DIR; #endif #ifdef CONFIG_MULTICORE_SPI2_TSCK_OUT dir |= F_TCLK_DIR; #endif #ifdef CONFIG_MULTICORE_SPI2_RSCK_OUT dir |= F_RCLK_DIR; #endif break; case 3: dir = F_RCLK_DIR | F_TCLK_DIR | F_TCS_DIR | F_RCS_DIR; #ifdef CONFIG_MULTICORE_SPI3_MOSI_OUT dir |= F_TD_DIR; #endif #ifdef CONFIG_MULTICORE_SPI3_MISO_OUT dir |= F_RD_DIR; #endif #ifdef CONFIG_MULTICORE_SPI3_SS0_OUT dir |= F_TCS_DIR; #endif #ifdef CONFIG_MULTICORE_SPI3_SS1_OUT dir |= F_RCS_DIR; #endif #ifdef CONFIG_MULTICORE_SPI3_TSCK_OUT dir |= F_TCLK_DIR; #endif #ifdef CONFIG_MULTICORE_SPI3_RSCK_OUT dir |= F_RCLK_DIR; #endif break; default: dir = 0; } _W(spic, RG_DIR, dir); printk("mc_spi: found controller #%d\n", master->bus_num); return 0; err_get_res: spi_master_put(master); err_return: return ret; } static __devexit int mc_spi_remove(struct platform_device *pdev) { struct spi_master *master = platform_get_drvdata(pdev); // !!! // Отключение аппаратуры (на случай, если remove была вызвана // без cleanup) #ifdef CONFIG_MULTICORE_SPI_DMA disable_irq(mfbsp_dma_rx_irq_num(master->bus_num)); free_irq(mfbsp_dma_rx_irq_num(master->bus_num), master); #endif spi_unregister_master(master); return 0; } static struct platform_driver mc_spi_driver = { .driver = { .name = "mc_spi", .owner = THIS_MODULE, }, .probe = mc_spi_probe, .remove = __devexit_p(mc_spi_remove), }; module_platform_driver(mc_spi_driver); MODULE_DESCRIPTION("Multicore SPI Controller driver"); MODULE_AUTHOR("Dmitry Podkhvatilin"); MODULE_LICENSE("GPL");