| /* |
| * linux/drivers/spi/spi-ambarella.c |
| * |
| * History: |
| * 2014/08/29 - [Zhenwu Xue] |
| |
| * |
| * Copyright (C) 2012-2016, Ambarella, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| #include <linux/version.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/io.h> |
| #include <linux/gpio.h> |
| #include <linux/clk.h> |
| #include <linux/platform_device.h> |
| #include <linux/err.h> |
| #include <linux/interrupt.h> |
| #include <linux/spi/spi.h> |
| #include <linux/spi/spidev.h> |
| #include <linux/dmaengine.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/dmapool.h> |
| #include <asm/io.h> |
| #include <mach/io.h> |
| #include <plat/spi.h> |
| #include <plat/dma.h> |
| #include <plat/rct.h> |
| |
| #define AMBARELLA_SPI_BUF_MAX_LEN (1024 * 20) //4096 |
| #define AMBARELLA_SPI_MAX_XFER_PER_MSG 32 |
| #define AMBARELLA_SPI_MAX_CS_NUM 8 |
| |
| struct ambarella_spi { |
| struct device *dev; |
| struct dma_chan *tx_dma_chan; |
| struct dma_chan *rx_dma_chan; |
| struct spi_message *msg; |
| struct spi_transfer *transfers[AMBARELLA_SPI_MAX_XFER_PER_MSG]; |
| struct clk *clk; |
| struct tasklet_struct tasklet; |
| void __iomem *virt; |
| |
| u8 *tx_dma_buf; |
| u8 *rx_dma_buf; |
| dma_addr_t tx_dma_phys; |
| dma_addr_t rx_dma_phys; |
| |
| u32 dma_buf_size; |
| u32 phys; |
| u32 clk_freq; |
| u32 dma_used:1; |
| u32 msb_first_only:1; |
| u32 ridx, widx; |
| u32 cspol; |
| int cs_pins[AMBARELLA_SPI_MAX_CS_NUM]; |
| int xfer_id, n_xfer; |
| int cs_active; |
| int rw; |
| int irq; |
| }; |
| |
| static void ambarella_spi_next_transfer(void *args); |
| |
| static int ambarella_spi_of_parse(struct platform_device *pdev, |
| struct spi_master *master) |
| { |
| struct device_node *np = pdev->dev.of_node; |
| struct ambarella_spi *ambspi = spi_master_get_devdata(master); |
| int rval; |
| |
| ambspi->clk = clk_get(NULL, "gclk_ssi"); |
| if (IS_ERR(ambspi->clk)) { |
| dev_err(&pdev->dev, "Get PLL failed!\n"); |
| return PTR_ERR(ambspi->clk); |
| } |
| |
| rval = of_property_read_u32(np, "amb,clk-freq", &ambspi->clk_freq); |
| if (rval < 0) { |
| dev_err(&pdev->dev, "invalid clk-freq! %d\n", rval); |
| return rval; |
| } |
| |
| return 0; |
| } |
| |
| static void ambarella_spi_setup(struct ambarella_spi *bus, struct spi_device *spi) |
| { |
| spi_ctrl0_reg_t cr0; |
| u32 ssi_clk, sckdv; |
| |
| cr0.w = 0; |
| cr0.s.dfs = spi->bits_per_word - 1; |
| if (spi->mode & SPI_CPHA) { |
| cr0.s.scph = 1; |
| } else { |
| cr0.s.scph = 0; |
| } |
| if (spi->mode & SPI_CPOL) { |
| cr0.s.scpol = 1; |
| } else { |
| cr0.s.scpol = 0; |
| } |
| if (spi->mode & SPI_LOOP) { |
| cr0.s.srl = 1; |
| } else { |
| cr0.s.srl = 0; |
| } |
| if (spi->mode & SPI_LSB_FIRST) { |
| cr0.s.tx_lsb = 1; |
| cr0.s.rx_lsb = 1; |
| } else { |
| cr0.s.tx_lsb = 0; |
| cr0.s.rx_lsb = 0; |
| } |
| |
| cr0.s.hold = 0; |
| cr0.s.byte_ws = 0; |
| cr0.s.fc_en = 0; |
| cr0.s.residue = 1; |
| amba_writel(bus->virt + SPI_CTRLR0_OFFSET, cr0.w); |
| |
| ssi_clk = bus->clk_freq; |
| if(spi->max_speed_hz > ssi_clk / 2) { |
| spi->max_speed_hz = ssi_clk / 2; |
| } |
| sckdv = (ssi_clk / spi->max_speed_hz + 1) & 0xfffe; |
| amba_writel(bus->virt + SPI_BAUDR_OFFSET, sckdv); |
| |
| bus->cspol = (spi->mode & SPI_CS_HIGH) ? 1 : 0; |
| gpio_set_value(spi->cs_gpio, bus->cspol); |
| bus->cs_active = 1; |
| } |
| |
| static void ambarella_spi_stop(struct ambarella_spi *bus) |
| { |
| amba_readl(bus->virt + SPI_ICR_OFFSET); |
| amba_readl(bus->virt + SPI_ISR_OFFSET); |
| |
| amba_writel(bus->virt + SPI_SSIENR_OFFSET, 0); |
| amba_writel(bus->virt + SPI_SER_OFFSET, 0); |
| |
| if (bus->dma_used) |
| amba_writel(bus->virt + SPI_DMAC_OFFSET, 0); |
| } |
| |
| static void ambarella_spi_prepare_transfer(struct ambarella_spi *bus) |
| { |
| struct spi_message *msg; |
| struct spi_transfer *xfer; |
| const void *wbuf, *rbuf; |
| spi_ctrl0_reg_t cr0; |
| |
| bus->widx = 0; |
| bus->ridx = 0; |
| |
| msg = bus->msg; |
| xfer = bus->transfers[bus->xfer_id]; |
| |
| wbuf = xfer->tx_buf; |
| rbuf = xfer->rx_buf; |
| if (wbuf && !rbuf) { |
| bus->rw = SPI_WRITE_ONLY; |
| } |
| if (!wbuf && rbuf) { |
| bus->rw = SPI_READ_ONLY; |
| } |
| if (wbuf && rbuf) { |
| bus->rw = SPI_WRITE_READ; |
| } |
| msg->actual_length += xfer->len; |
| |
| cr0.w = amba_readl(bus->virt + SPI_CTRLR0_OFFSET); |
| cr0.s.tmod = SPI_WRITE_READ; |
| amba_writel(bus->virt + SPI_CTRLR0_OFFSET, cr0.w); |
| |
| if (!bus->cs_active) { |
| gpio_set_value(msg->spi->cs_gpio, bus->cspol); |
| bus->cs_active = 1; |
| } |
| |
| amba_writel(bus->virt + SPI_SSIENR_OFFSET, 0); |
| amba_writel(bus->virt + SPI_SER_OFFSET, 0); |
| if (bus->dma_used) { |
| if (bus->msg->spi->bits_per_word <= 8) { |
| amba_writel(bus->virt + SPI_RXFTLR_OFFSET, 8 - 1); |
| } else { |
| amba_writel(bus->virt + SPI_RXFTLR_OFFSET, 4 - 1); |
| } |
| amba_writel(bus->virt + SPI_DMAC_OFFSET, 0x3); |
| } else { |
| disable_irq_nosync(bus->irq); |
| amba_writel(bus->virt + SPI_IMR_OFFSET, SPI_TXEIS_MASK); |
| amba_writel(bus->virt + SPI_TXFTLR_OFFSET, 0); |
| amba_writel(bus->virt + SPI_RXFTLR_OFFSET, 1); |
| } |
| amba_writel(bus->virt + SPI_SSIENR_OFFSET, 1); |
| } |
| |
| static void ambarella_spi_start_transfer(struct ambarella_spi *bus) |
| { |
| struct spi_device *spi; |
| struct spi_transfer *xfer; |
| struct dma_slave_config tx_cfg, rx_cfg; |
| struct dma_async_tx_descriptor *txd, *rxd; |
| u32 len, widx, ridx, xfer_len, i; |
| void *wbuf, *rbuf; |
| u16 tmp; |
| |
| xfer = bus->transfers[bus->xfer_id]; |
| spi = bus->msg->spi; |
| |
| wbuf = (void *)xfer->tx_buf; |
| rbuf = (void *)xfer->rx_buf; |
| widx = bus->widx; |
| ridx = bus->ridx; |
| |
| len = xfer->len; |
| if (!bus->dma_used) { |
| if (bus->msg->spi->bits_per_word > 8) |
| len >>= 1; |
| } |
| |
| dma_sync_single_for_cpu(bus->dev, bus->tx_dma_phys, len, DMA_TO_DEVICE); |
| |
| switch (bus->rw) { |
| case SPI_WRITE_ONLY: |
| case SPI_WRITE_READ: |
| if (!bus->dma_used) { |
| xfer_len = min_t(int, len - widx, SPI_DATA_FIFO_SIZE_16); |
| for(i = 0; i < xfer_len; i++) { |
| if (bus->msg->spi->bits_per_word <= 8) |
| tmp = ((u8 *)wbuf)[widx++]; |
| else |
| tmp = ((u16 *)wbuf)[widx++]; |
| amba_writel(bus->virt + SPI_DR_OFFSET, tmp); |
| } |
| } else { |
| memcpy(bus->tx_dma_buf, xfer->tx_buf, len); |
| } |
| break; |
| case SPI_READ_ONLY: |
| if (!bus->dma_used) { |
| xfer_len = min_t(int, len - ridx, SPI_DATA_FIFO_SIZE_16); |
| for(i = 0; i < xfer_len; i++) |
| amba_writel(bus->virt + SPI_DR_OFFSET, SPI_DUMMY_DATA); |
| } else { |
| memset(bus->tx_dma_buf, 0xFF, len); |
| } |
| |
| break; |
| default: |
| break; |
| } |
| |
| if (bus->dma_used) { |
| /* TX DMA */ |
| tx_cfg.dst_addr = bus->phys + SPI_DR_OFFSET; |
| if (spi->bits_per_word <= 8) { |
| tx_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; |
| } else { |
| tx_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; |
| } |
| tx_cfg.dst_maxburst = 8; |
| tx_cfg.direction = DMA_MEM_TO_DEV; |
| |
| BUG_ON(dmaengine_slave_config(bus->tx_dma_chan, &tx_cfg) < 0); |
| |
| dma_sync_single_for_device(bus->dev, bus->tx_dma_phys, len, DMA_TO_DEVICE); |
| |
| txd = dmaengine_prep_slave_single(bus->tx_dma_chan, bus->tx_dma_phys, len, |
| DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | |
| DMA_COMPL_SKIP_SRC_UNMAP | DMA_COMPL_SKIP_DEST_UNMAP | DMA_CTRL_ACK); |
| BUG_ON (!txd); |
| |
| txd->callback = NULL; |
| txd->callback_param = NULL; |
| dmaengine_submit(txd); |
| |
| dma_async_issue_pending(bus->tx_dma_chan); |
| |
| /* RX DMA */ |
| rx_cfg.src_addr = bus->phys + SPI_DR_OFFSET; |
| if (spi->bits_per_word <= 8) { |
| rx_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; |
| } else { |
| rx_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; |
| } |
| rx_cfg.src_maxburst = 8; |
| rx_cfg.direction = DMA_DEV_TO_MEM; |
| |
| BUG_ON(dmaengine_slave_config(bus->rx_dma_chan, &rx_cfg) < 0); |
| |
| rxd = dmaengine_prep_slave_single(bus->rx_dma_chan, bus->rx_dma_phys, len, |
| DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK | |
| DMA_COMPL_SKIP_SRC_UNMAP | DMA_COMPL_SKIP_DEST_UNMAP); |
| BUG_ON(!rxd); |
| |
| rxd->callback = ambarella_spi_next_transfer; |
| rxd->callback_param = bus; |
| |
| dma_sync_single_for_device(bus->dev, bus->rx_dma_phys, len, DMA_FROM_DEVICE); |
| |
| dmaengine_submit(rxd); |
| dma_async_issue_pending(bus->rx_dma_chan); |
| } else { |
| bus->widx = widx; |
| enable_irq(bus->irq); |
| } |
| |
| amba_writel(bus->virt + SPI_SER_OFFSET, 1 << spi->chip_select); |
| } |
| |
| static void ambarella_spi_next_transfer(void *args) |
| { |
| struct ambarella_spi *bus = args; |
| struct spi_transfer *xfer; |
| |
| xfer = bus->transfers[bus->xfer_id++]; |
| |
| if (bus->dma_used) { |
| switch (bus->rw) { |
| case SPI_WRITE_READ: |
| case SPI_READ_ONLY: |
| dma_sync_single_for_cpu(bus->dev, bus->rx_dma_phys, xfer->len, DMA_FROM_DEVICE); |
| memcpy(xfer->rx_buf, bus->rx_dma_buf, xfer->len); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (xfer->cs_change) { |
| gpio_set_value(bus->msg->spi->cs_gpio, bus->cspol^1); |
| bus->cs_active = 0; |
| } |
| |
| if (bus->xfer_id >= bus->n_xfer) { |
| gpio_set_value(bus->msg->spi->cs_gpio, bus->cspol^1); |
| bus->cs_active = 0; |
| |
| ambarella_spi_stop(bus); |
| spi_finalize_current_message(bus->msg->spi->master); |
| } else { |
| ambarella_spi_prepare_transfer(bus); |
| ambarella_spi_start_transfer(bus); |
| } |
| } |
| |
| static void ambarella_spi_tasklet(unsigned long data) |
| { |
| struct ambarella_spi *bus = (struct ambarella_spi *)data; |
| struct spi_transfer *xfer; |
| void *rbuf; |
| u32 widx, ridx, len, rxflr, xfer_len; |
| u32 status, finish_transfer = 0; |
| u16 i, tmp; |
| |
| xfer = bus->transfers[bus->xfer_id]; |
| |
| /* Wait until SPI idle */ |
| status = amba_readl(bus->virt + SPI_SR_OFFSET); |
| if (status & 0x1) { |
| /* Transfer is still in progress */ |
| for (i = 0; i < MAX_QUERY_TIMES; i++) { |
| status = amba_readl(bus->virt + SPI_SR_OFFSET); |
| if (!(status & 0x1)) |
| break; |
| } |
| if (status & 0x1) { |
| tasklet_schedule(&bus->tasklet); |
| return; |
| } |
| } |
| |
| rbuf = (void *)xfer->rx_buf; |
| widx = bus->widx; |
| ridx = bus->ridx; |
| len = xfer->len; |
| |
| if (bus->msg->spi->bits_per_word > 8) |
| len >>= 1; |
| |
| /* Fetch data from FIFO */ |
| switch (bus->rw) { |
| case SPI_READ_ONLY: |
| case SPI_WRITE_READ: |
| xfer_len = len - ridx; |
| rxflr = amba_readl(bus->virt + SPI_RXFLR_OFFSET); |
| if (xfer_len > rxflr) |
| xfer_len = rxflr; |
| for(i = 0; i < xfer_len; i++) { |
| tmp = amba_readl(bus->virt + SPI_DR_OFFSET); |
| if (bus->msg->spi->bits_per_word <= 8) |
| ((u8 *)rbuf)[ridx++] = tmp & 0xff; |
| else |
| ((u16 *)rbuf)[ridx++] = tmp; |
| } |
| bus->ridx = ridx; |
| break; |
| default: |
| break; |
| } |
| |
| /* Check whether the current transfer ends */ |
| switch (bus->rw) { |
| case SPI_WRITE_ONLY: |
| if (widx == len) |
| finish_transfer = 1; |
| break; |
| case SPI_READ_ONLY: |
| if (ridx == len) |
| finish_transfer = 1; |
| break; |
| case SPI_WRITE_READ: |
| if (ridx == len && widx == len) |
| finish_transfer = 1; |
| break; |
| default: |
| break; |
| } |
| |
| /* End transfer or continue filling FIFO */ |
| if (finish_transfer) { |
| ambarella_spi_next_transfer((void *) bus); |
| enable_irq(bus->irq); |
| } else { |
| ambarella_spi_start_transfer(bus); |
| } |
| } |
| |
| static int ambarella_spi_one_message(struct spi_master *master, struct spi_message *msg) |
| { |
| int err = 0; |
| struct spi_device *spi; |
| struct ambarella_spi *bus; |
| struct spi_transfer *xfer; |
| |
| spi = msg->spi; |
| bus = spi_master_get_devdata(master); |
| |
| msg->status = 0; |
| msg->actual_length = 0; |
| bus->msg = msg; |
| |
| if (list_empty(&msg->transfers)) { |
| spi_finalize_current_message(master); |
| goto ambarella_spi_transfer_exit; |
| } |
| |
| if (!gpio_is_valid(spi->cs_gpio)) { |
| dev_err(&master->dev, "cs %d is invalid!\n", spi->cs_gpio); |
| err = -EINVAL; |
| goto ambarella_spi_transfer_exit; |
| } |
| |
| if (spi->bits_per_word < 4 || spi->bits_per_word > 16) { |
| err = -EINVAL; |
| goto ambarella_spi_transfer_exit; |
| } |
| |
| if (!spi->max_speed_hz) { |
| err = -EINVAL; |
| goto ambarella_spi_transfer_exit; |
| } |
| |
| if (spi->bits_per_word > 8) { |
| list_for_each_entry(xfer, &msg->transfers, transfer_list) { |
| if (xfer->len & 0x1) { |
| err = -EINVAL; |
| goto ambarella_spi_transfer_exit; |
| } |
| } |
| } |
| |
| if ((spi->mode & SPI_LSB_FIRST) && bus->msb_first_only) { |
| dev_err(&master->dev, "SPI[%d] only supports msb first tx-rx", master->bus_num); |
| err = -EINVAL; |
| goto ambarella_spi_transfer_exit; |
| } |
| |
| bus->n_xfer = 0; |
| bus->xfer_id = 0; |
| list_for_each_entry(xfer, &msg->transfers, transfer_list) { |
| if (xfer->len > AMBARELLA_SPI_BUF_MAX_LEN) { |
| err = -EINVAL; |
| goto ambarella_spi_transfer_exit; |
| } |
| bus->transfers[bus->n_xfer++] = xfer; |
| } |
| |
| ambarella_spi_setup(bus, spi); |
| ambarella_spi_prepare_transfer(bus); |
| ambarella_spi_start_transfer(bus); |
| |
| ambarella_spi_transfer_exit: |
| return err; |
| } |
| |
| static irqreturn_t ambarella_spi_isr(int irq, void *dev_data) |
| { |
| struct ambarella_spi *bus = dev_data; |
| |
| if (amba_readl(bus->virt + SPI_ISR_OFFSET)) { |
| disable_irq_nosync(bus->irq); |
| amba_writel(bus->virt + SPI_ISR_OFFSET, 0); |
| |
| ambarella_spi_tasklet((unsigned long)bus); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int ambarella_spi_dma_channel_allocate(struct ambarella_spi *bus, |
| bool dma_to_memory) |
| { |
| struct dma_chan *dma_chan; |
| dma_addr_t dma_phys; |
| u8 *dma_buf; |
| int ret = 0; |
| |
| dma_chan = dma_request_slave_channel(bus->dev, |
| dma_to_memory ? "rx" : "tx"); |
| if (IS_ERR(dma_chan)) { |
| ret = PTR_ERR(dma_chan); |
| if (ret != -EPROBE_DEFER) |
| dev_err(bus->dev, |
| "Dma channel is not available: %d\n", ret); |
| return ret; |
| } |
| |
| dma_buf = dma_alloc_coherent(bus->dev, bus->dma_buf_size, |
| &dma_phys, GFP_KERNEL); |
| if (!dma_buf) { |
| dev_err(bus->dev, " Not able to allocate the dma buffer\n"); |
| dma_release_channel(dma_chan); |
| return -ENOMEM; |
| } |
| |
| if (dma_to_memory) { |
| bus->rx_dma_chan = dma_chan; |
| bus->rx_dma_buf = dma_buf; |
| bus->rx_dma_phys = dma_phys; |
| } else { |
| bus->tx_dma_chan = dma_chan; |
| bus->tx_dma_buf = dma_buf; |
| bus->tx_dma_phys = dma_phys; |
| } |
| |
| return ret; |
| } |
| |
| static void ambarella_spi_dma_channel_free(struct ambarella_spi *bus, |
| bool dma_to_memory) |
| { |
| u8 *dma_buf; |
| dma_addr_t dma_phys; |
| struct dma_chan *dma_chan; |
| |
| if (dma_to_memory) { |
| dma_buf = bus->rx_dma_buf; |
| dma_chan = bus->rx_dma_chan; |
| dma_phys = bus->rx_dma_phys; |
| bus->rx_dma_chan = NULL; |
| bus->rx_dma_buf = NULL; |
| } else { |
| dma_buf = bus->tx_dma_buf; |
| dma_chan = bus->tx_dma_chan; |
| dma_phys = bus->tx_dma_phys; |
| bus->tx_dma_buf = NULL; |
| bus->tx_dma_chan = NULL; |
| } |
| if (!dma_chan) |
| return; |
| |
| dma_free_coherent(bus->dev, bus->dma_buf_size, dma_buf, dma_phys); |
| dma_release_channel(dma_chan); |
| } |
| |
| static int ambarella_spi_hw_setup(struct spi_device *spi) |
| { |
| struct ambarella_spi *bus = spi_master_get_devdata(spi->master); |
| int err = 0; |
| |
| if (gpio_is_valid(spi->cs_gpio) && |
| (bus->cs_pins[spi->chip_select] != spi->cs_gpio)) { |
| err = gpio_request(spi->cs_gpio, dev_name(&spi->dev)); |
| if (err < 0) { |
| dev_err(&spi->dev, "can't get CS: %d\n", err); |
| return -EINVAL; |
| } |
| gpio_direction_output(spi->cs_gpio, (spi->mode & SPI_CS_HIGH) ? 0 : 1); |
| bus->cs_pins[spi->chip_select] = spi->cs_gpio; |
| } |
| |
| return 0; |
| } |
| |
| static int ambarella_spi_probe(struct platform_device *pdev) |
| { |
| struct resource *res; |
| void __iomem *reg; |
| struct ambarella_spi *bus; |
| struct spi_master *master; |
| int i; |
| int err = 0; |
| int irq; |
| u32 val; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| err = -EINVAL; |
| goto exit_spi_probe; |
| } |
| |
| reg = devm_ioremap(&pdev->dev, res->start, resource_size(res)); |
| if (!reg) { |
| err = -ENOMEM; |
| goto exit_spi_probe; |
| } |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) { |
| dev_err(&pdev->dev, "no irq resource!\n"); |
| err = -ENODEV; |
| goto exit_spi_probe; |
| } |
| |
| master = spi_alloc_master(&pdev->dev, sizeof(*bus)); |
| if (!master) { |
| err = -ENOMEM; |
| goto exit_spi_probe; |
| } |
| |
| bus = spi_master_get_devdata(master); |
| bus->phys = res->start; |
| bus->virt = reg; |
| bus->irq = irq; |
| bus->dev = &pdev->dev; |
| |
| for (i = 0; i < AMBARELLA_SPI_MAX_CS_NUM; i++) |
| bus->cs_pins[i] = -1; |
| |
| err = ambarella_spi_of_parse(pdev, master); |
| if (err < 0) { |
| goto exit_free_master; |
| } |
| |
| clk_set_rate(bus->clk, bus->clk_freq); |
| amba_writel(reg + SPI_SSIENR_OFFSET, 0); |
| amba_writel(reg + SPI_IMR_OFFSET, 0); |
| |
| master->dev.of_node = pdev->dev.of_node; |
| master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH; |
| master->transfer_one_message = ambarella_spi_one_message; |
| master->setup = ambarella_spi_hw_setup; |
| platform_set_drvdata(pdev, master); |
| |
| /* check if hw only supports msb first tx/rx */ |
| if (of_find_property(pdev->dev.of_node, "amb,msb-first-only", NULL)) { |
| bus->msb_first_only = 1; |
| dev_info(&pdev->dev,"Only supports msb first tx-rx\n"); |
| } else { |
| bus->msb_first_only = 0; |
| master->mode_bits |= SPI_LSB_FIRST; |
| } |
| |
| /* check if using dma */ |
| if (of_find_property(pdev->dev.of_node, "amb,dma-used", NULL)) { |
| bus->dma_used = 1; |
| dev_info(&pdev->dev,"DMA is used\n"); |
| |
| /* Enable DMA Channel 0/1 as SSI0 Tx and Rx */ |
| val = amba_readl(AHB_SCRATCHPAD_REG(0x0c)); |
| val &= 0xff9fffff; |
| val |= 0x00200000; |
| amba_writel(AHB_SCRATCHPAD_REG(0x0c), val); |
| |
| bus->dma_buf_size = AMBARELLA_SPI_BUF_MAX_LEN; |
| err = ambarella_spi_dma_channel_allocate(bus, false); |
| if (err < 0) |
| goto exit_free_master; |
| err = ambarella_spi_dma_channel_allocate(bus, true); |
| if (err < 0) |
| goto exit_tx_dma_irq_free; |
| } else { |
| bus->dma_used = 0; |
| /* request IRQ */ |
| err = devm_request_irq(&pdev->dev, irq, ambarella_spi_isr, |
| IRQF_TRIGGER_HIGH, dev_name(&pdev->dev), bus); |
| if (err) |
| goto exit_free_master; |
| |
| tasklet_init(&bus->tasklet, ambarella_spi_tasklet, (unsigned long)bus); |
| } |
| |
| err = spi_register_master(master); |
| if (err) |
| goto exit_rx_dma_free; |
| |
| dev_info(&pdev->dev, "Ambarella spi controller %d created.\r\n", master->bus_num); |
| |
| return err; |
| |
| exit_rx_dma_free: |
| if (bus->dma_used) |
| ambarella_spi_dma_channel_free(bus, true); |
| exit_tx_dma_irq_free: |
| if (bus->dma_used) |
| ambarella_spi_dma_channel_free(bus, false); |
| else |
| free_irq(irq, bus); |
| exit_free_master: |
| spi_master_put(master); |
| exit_spi_probe: |
| return err; |
| } |
| |
| static int ambarella_spi_remove(struct platform_device *pdev) |
| { |
| struct spi_master *master; |
| struct ambarella_spi *bus; |
| int i; |
| |
| master = platform_get_drvdata(pdev); |
| bus = spi_master_get_devdata(master); |
| |
| if (!bus->dma_used) |
| tasklet_kill(&bus->tasklet); |
| else { |
| if (bus->tx_dma_chan) |
| ambarella_spi_dma_channel_free(bus, false); |
| |
| if (bus->rx_dma_chan) |
| ambarella_spi_dma_channel_free(bus, true); |
| } |
| |
| ambarella_spi_stop(bus); |
| |
| for (i = 0; i < AMBARELLA_SPI_MAX_CS_NUM; i++) { |
| if (gpio_is_valid(bus->cs_pins[i])) |
| gpio_free(bus->cs_pins[i]); |
| } |
| |
| spi_unregister_master(master); |
| |
| return 0; |
| } |
| |
| int ambarella_spi_write(amba_spi_cfg_t *spi_cfg, amba_spi_write_t *spi_w) |
| { |
| int err = 0; |
| struct spi_master *master; |
| struct spi_device spi; |
| struct ambarella_spi *bus; |
| |
| master = spi_busnum_to_master(spi_w->bus_id); |
| if (!master) { |
| err = -EINVAL; |
| goto ambarella_spi_write_exit; |
| } |
| |
| bus = spi_master_get_devdata(master); |
| |
| spi.master = master; |
| spi.bits_per_word = spi_cfg->cfs_dfs; |
| spi.mode = spi_cfg->spi_mode; |
| spi.max_speed_hz = spi_cfg->baud_rate; |
| spi.chip_select = spi_w->cs_id; |
| spi.cs_gpio = bus->cs_pins[spi.chip_select]; |
| |
| err = spi_write_then_read(&spi, spi_w->buffer, spi_w->n_size, NULL, 0); |
| |
| ambarella_spi_write_exit: |
| return err; |
| } |
| EXPORT_SYMBOL(ambarella_spi_write); |
| |
| int ambarella_spi_read(amba_spi_cfg_t *spi_cfg, amba_spi_read_t *spi_r) |
| { |
| int err = 0; |
| struct spi_master *master; |
| struct spi_device spi; |
| struct ambarella_spi *bus; |
| |
| master = spi_busnum_to_master(spi_r->bus_id); |
| if (!master) { |
| err = -EINVAL; |
| goto ambarella_spi_read_exit; |
| } |
| |
| bus = spi_master_get_devdata(master); |
| |
| spi.master = master; |
| spi.bits_per_word = spi_cfg->cfs_dfs; |
| spi.mode = spi_cfg->spi_mode; |
| spi.max_speed_hz = spi_cfg->baud_rate; |
| spi.chip_select = spi_r->cs_id; |
| spi.cs_gpio = bus->cs_pins[spi.chip_select]; |
| |
| err = spi_write_then_read(&spi, NULL, 0, spi_r->buffer, spi_r->n_size); |
| |
| ambarella_spi_read_exit: |
| return err; |
| } |
| EXPORT_SYMBOL(ambarella_spi_read); |
| |
| int ambarella_spi_write_then_read(amba_spi_cfg_t *spi_cfg, |
| amba_spi_write_then_read_t *spi_wtr) |
| { |
| int err = 0; |
| struct spi_master *master; |
| struct spi_device spi; |
| struct ambarella_spi *bus; |
| |
| master = spi_busnum_to_master(spi_wtr->bus_id); |
| if (!master) { |
| err = -EINVAL; |
| goto ambarella_spi_write_then_read_exit; |
| } |
| |
| bus = spi_master_get_devdata(master); |
| |
| spi.master = master; |
| spi.bits_per_word = spi_cfg->cfs_dfs; |
| spi.mode = spi_cfg->spi_mode; |
| spi.max_speed_hz = spi_cfg->baud_rate; |
| spi.chip_select = spi_wtr->cs_id; |
| spi.cs_gpio = bus->cs_pins[spi.chip_select]; |
| |
| err = spi_write_then_read(&spi, |
| spi_wtr->w_buffer, spi_wtr->w_size, |
| spi_wtr->r_buffer, spi_wtr->r_size); |
| |
| ambarella_spi_write_then_read_exit: |
| return err; |
| } |
| EXPORT_SYMBOL(ambarella_spi_write_then_read); |
| |
| int ambarella_spi_write_and_read(amba_spi_cfg_t *spi_cfg, |
| amba_spi_write_and_read_t *spi_war) |
| { |
| int err = 0; |
| struct spi_master *master; |
| struct spi_device spi; |
| struct ambarella_spi *bus; |
| struct spi_message msg; |
| struct spi_transfer x[1]; |
| |
| master = spi_busnum_to_master(spi_war->bus_id); |
| if (!master) { |
| err = -EINVAL; |
| goto ambarella_spi_write_and_read_exit; |
| } |
| |
| bus = spi_master_get_devdata(master); |
| |
| spi.master = master; |
| spi.bits_per_word = spi_cfg->cfs_dfs; |
| spi.mode = spi_cfg->spi_mode; |
| spi.max_speed_hz = spi_cfg->baud_rate; |
| spi.chip_select = spi_war->cs_id; |
| spi.cs_gpio = bus->cs_pins[spi.chip_select]; |
| |
| spi_message_init(&msg); |
| memset(x, 0, sizeof x); |
| x->tx_buf = spi_war->w_buffer; |
| x->rx_buf = spi_war->r_buffer; |
| x->len = spi_war->n_size; |
| spi_message_add_tail(x, &msg); |
| |
| err = spi_sync(&spi, &msg); |
| |
| ambarella_spi_write_and_read_exit: |
| return err; |
| } |
| EXPORT_SYMBOL(ambarella_spi_write_and_read); |
| |
| static const struct of_device_id ambarella_spi_dt_ids[] = { |
| {.compatible = "ambarella,spi", }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, ambarella_spi_dt_ids); |
| |
| static struct platform_driver ambarella_spi_driver = { |
| .probe = ambarella_spi_probe, |
| .remove = ambarella_spi_remove, |
| .driver = { |
| .name = "ambarella-spi", |
| .owner = THIS_MODULE, |
| .of_match_table = ambarella_spi_dt_ids, |
| }, |
| }; |
| |
| static int ambarella_spi_init(void) |
| { |
| return platform_driver_register(&ambarella_spi_driver); |
| } |
| |
| static void ambarella_spi_exit(void) |
| { |
| platform_driver_unregister(&ambarella_spi_driver); |
| } |
| |
| module_init(ambarella_spi_init); |
| module_exit(ambarella_spi_exit); |
| |
| MODULE_DESCRIPTION("Ambarella Media Processor SPI Bus Controller"); |
| MODULE_AUTHOR("Zhenwu Xue, <zwxue@ambarella.com>"); |
| MODULE_LICENSE("GPL"); |