| /* |
| * linux/drivers/spi/spi_ambarella.c |
| * |
| * History: |
| * 2008/03/03 - [Louis Sun] created file |
| * 2009/06/19 - [Zhenwu Xue] ported from 2.6.22.10 |
| |
| * |
| * Copyright (C) 2004-2012, 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/platform_device.h> |
| #include <linux/err.h> |
| #include <linux/interrupt.h> |
| #include <linux/spi/spi.h> |
| #include <linux/spi/spidev.h> |
| #include <asm/io.h> |
| #include <mach/io.h> |
| #include <plat/spi.h> |
| #include <plat/ambcache.h> |
| |
| /*============================Global Variables================================*/ |
| |
| struct ambarella_spi { |
| u32 regbase; |
| struct ambarella_spi_platform_info *pinfo; |
| |
| int irq; |
| struct tasklet_struct tasklet; |
| |
| spinlock_t lock; |
| struct list_head queue; |
| u32 idle; |
| u32 shutdown; |
| |
| struct spi_device *c_dev; |
| struct spi_message *c_msg; |
| struct spi_transfer *c_xfer; |
| |
| u8 rw_mode, bpw, chip_select; |
| u32 ridx, widx, len; |
| }; |
| |
| struct ambarella_spi_private { |
| struct spi_device *spi; |
| struct mutex mtx; |
| spinlock_t lock; |
| }; |
| |
| static struct { |
| int cs_num; |
| struct ambarella_spi_private *data; |
| } ambarella_spi_private_devices[SPI_MASTER_INSTANCES]; |
| |
| static void ambarella_spi_handle_message(struct ambarella_spi *); |
| static void ambarella_spi_prepare_message(struct ambarella_spi *); |
| static void ambarella_spi_prepare_transfer(struct ambarella_spi *); |
| static void ambarella_spi_finish_transfer(struct ambarella_spi *); |
| static void ambarella_spi_finish_message(struct ambarella_spi *); |
| static void ambarella_spi_start_transfer(struct ambarella_spi *); |
| |
| /*============================SPI Bus Driver==================================*/ |
| static int ambarella_spi_setup(struct spi_device *spi) |
| { |
| return 0; |
| } |
| |
| static int ambarella_spi_stop(struct ambarella_spi *priv) |
| { |
| amba_readl(priv->regbase + SPI_ICR_OFFSET); |
| amba_readl(priv->regbase + SPI_ISR_OFFSET); |
| amba_writel(priv->regbase + SPI_SER_OFFSET, 0); |
| amba_writel(priv->regbase + SPI_SSIENR_OFFSET, 0); |
| |
| return 0; |
| } |
| |
| static void ambarella_spi_start_transfer(struct ambarella_spi *priv) |
| { |
| void *wbuf; |
| u32 widx, ridx, len; |
| u32 xfer_len; |
| u8 cs_id; |
| u16 i, tmp; |
| |
| wbuf = (void *)priv->c_xfer->tx_buf; |
| len = priv->len; |
| cs_id = priv->c_dev->chip_select; |
| widx = priv->widx; |
| ridx = priv->ridx; |
| |
| /* Feed data into FIFO */ |
| switch (priv->rw_mode) { |
| case SPI_WRITE_ONLY: |
| xfer_len = len - widx; |
| if (xfer_len > priv->pinfo->fifo_entries) |
| xfer_len = priv->pinfo->fifo_entries; |
| |
| amba_writel(priv->regbase + SPI_SER_OFFSET, 0); |
| if (priv->bpw <= 8) { |
| for(i = 0; i < xfer_len; i++) { |
| tmp = ((u8 *)wbuf)[widx++]; |
| amba_writel(priv->regbase + SPI_DR_OFFSET, tmp); |
| } |
| } else{ |
| for(i = 0; i < xfer_len; i++) { |
| tmp = ((u16 *)wbuf)[widx++]; |
| amba_writel(priv->regbase + SPI_DR_OFFSET, tmp); |
| } |
| } |
| amba_writel(priv->regbase + SPI_SER_OFFSET, 1 << cs_id); |
| |
| break; |
| |
| case SPI_WRITE_READ: |
| xfer_len = len - widx; |
| if (xfer_len > priv->pinfo->fifo_entries) |
| xfer_len = priv->pinfo->fifo_entries; |
| |
| amba_writel(priv->regbase + SPI_SER_OFFSET, 0); |
| if (priv->bpw <= 8) { |
| for(i = 0; i < xfer_len; i++) { |
| tmp = ((u8 *)wbuf)[widx++]; |
| amba_writel(priv->regbase + SPI_DR_OFFSET, tmp); |
| } |
| } else{ |
| for(i = 0; i < xfer_len; i++) { |
| tmp = ((u16 *)wbuf)[widx++]; |
| amba_writel(priv->regbase + SPI_DR_OFFSET, tmp); |
| } |
| } |
| amba_writel(priv->regbase + SPI_SER_OFFSET, 1 << cs_id); |
| |
| break; |
| |
| case SPI_READ_ONLY: |
| xfer_len = len - ridx; |
| if (xfer_len > priv->pinfo->fifo_entries) |
| xfer_len = priv->pinfo->fifo_entries; |
| |
| amba_writel(priv->regbase + SPI_SER_OFFSET, 0); |
| for(i = 0; i < xfer_len; i++) |
| amba_writel(priv->regbase + SPI_DR_OFFSET, SPI_DUMMY_DATA); |
| amba_writel(priv->regbase + SPI_SER_OFFSET, 1 << cs_id); |
| |
| break; |
| |
| default: |
| break; |
| } |
| |
| priv->widx = widx; |
| enable_irq(priv->irq); |
| |
| return; |
| } |
| |
| static void ambarella_spi_tasklet(unsigned long data) |
| { |
| struct ambarella_spi *priv = (struct ambarella_spi *)data; |
| void *rbuf; |
| u32 widx, ridx, len; |
| u32 rxflr, xfer_len; |
| u32 status; |
| u16 i, tmp; |
| u32 finish_transfer; |
| |
| /* Wait until SPI idle */ |
| status = amba_readl(priv->regbase + SPI_SR_OFFSET); |
| if (status & 0x1) { |
| /* Transfer is still in progress */ |
| for (i = 0; i < MAX_QUERY_TIMES; i++) { |
| status = amba_readl(priv->regbase + SPI_SR_OFFSET); |
| if (!(status & 0x1)) |
| break; |
| } |
| if (status & 0x1) { |
| tasklet_schedule(&priv->tasklet); |
| return; |
| } |
| } |
| |
| rbuf = (void *)priv->c_xfer->rx_buf; |
| len = priv->len; |
| widx = priv->widx; |
| ridx = priv->ridx; |
| |
| /* Fetch data from FIFO */ |
| switch (priv->rw_mode) { |
| case SPI_READ_ONLY: |
| case SPI_WRITE_READ: |
| xfer_len = len - ridx; |
| rxflr = amba_readl(priv->regbase + SPI_RXFLR_OFFSET); |
| if (xfer_len > rxflr) |
| xfer_len = rxflr; |
| |
| if (priv->bpw <= 8) { |
| for(i = 0; i < xfer_len; i++) { |
| tmp = amba_readl(priv->regbase + SPI_DR_OFFSET); |
| ((u8 *)rbuf)[ridx++] = tmp & 0xff; |
| } |
| } else { |
| for(i = 0; i < xfer_len; i++){ |
| tmp = amba_readl(priv->regbase + SPI_DR_OFFSET); |
| ((u16 *)rbuf)[ridx++] = tmp; |
| } |
| } |
| |
| priv->ridx = ridx; |
| break; |
| |
| default: |
| break; |
| } |
| |
| /* Check whether the current transfer ends */ |
| finish_transfer = 0; |
| switch (priv->rw_mode) { |
| 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_finish_transfer(priv); |
| enable_irq(priv->irq); |
| } else { |
| ambarella_spi_start_transfer(priv); |
| } |
| } |
| |
| static void ambarella_spi_prepare_transfer(struct ambarella_spi *priv) |
| { |
| struct spi_message *msg; |
| struct spi_transfer *xfer; |
| struct ambarella_spi_cs_config cs_config; |
| u32 ctrlr0; |
| void *wbuf, *rbuf; |
| |
| msg = priv->c_msg; |
| xfer = list_entry(msg->transfers.next, struct spi_transfer, transfer_list); |
| priv->c_xfer = xfer; |
| list_del(msg->transfers.next); |
| |
| wbuf = (void *)xfer->tx_buf; |
| rbuf = (void *)xfer->rx_buf; |
| if (priv->bpw <= 8) |
| priv->len = xfer->len; |
| else |
| priv->len = xfer->len >> 1; |
| priv->widx = 0; |
| priv->ridx = 0; |
| if (wbuf && !rbuf) |
| priv->rw_mode = SPI_WRITE_ONLY; |
| if ( !wbuf && rbuf) |
| priv->rw_mode = SPI_READ_ONLY; |
| if (wbuf && rbuf) |
| priv->rw_mode = SPI_WRITE_READ; |
| |
| ctrlr0 = amba_readl(priv->regbase + SPI_CTRLR0_OFFSET); |
| ctrlr0 &= 0xfffff4ff; |
| /* Always use write & read mode due to I1 changes */ |
| ctrlr0 |= (SPI_WRITE_READ << 8); |
| if (priv->c_dev->mode & SPI_LOOP) |
| ctrlr0 |= (0x1 << 11); |
| |
| amba_writel(priv->regbase + SPI_CTRLR0_OFFSET, ctrlr0); |
| |
| if (!priv->chip_select) { |
| cs_config.bus_id = priv->c_dev->master->bus_num; |
| cs_config.cs_id = priv->c_dev->chip_select; |
| cs_config.cs_num = priv->c_dev->master->num_chipselect; |
| cs_config.cs_pins = priv->pinfo->cs_pins; |
| priv->pinfo->cs_activate(&cs_config); |
| priv->chip_select = 1; |
| } |
| |
| disable_irq_nosync(priv->irq); |
| amba_writel(priv->regbase + SPI_IMR_OFFSET, SPI_TXEIS_MASK); |
| amba_writel(priv->regbase + SPI_SSIENR_OFFSET, 1); |
| amba_writel(priv->regbase + SPI_SER_OFFSET, 0); |
| } |
| |
| static void ambarella_spi_finish_transfer(struct ambarella_spi *priv) |
| { |
| if (priv->c_xfer->cs_change) { |
| struct ambarella_spi_cs_config cs_config; |
| |
| cs_config.bus_id = priv->c_dev->master->bus_num; |
| cs_config.cs_id = priv->c_msg->spi->chip_select; |
| cs_config.cs_num = priv->c_dev->master->num_chipselect; |
| cs_config.cs_pins = priv->pinfo->cs_pins; |
| |
| priv->pinfo->cs_deactivate(&cs_config); |
| priv->chip_select = 0; |
| } |
| ambarella_spi_stop(priv); |
| |
| if (list_empty(&priv->c_msg->transfers)) { |
| ambarella_spi_finish_message(priv); |
| } else { |
| ambarella_spi_prepare_transfer(priv); |
| ambarella_spi_start_transfer(priv); |
| } |
| } |
| |
| static void ambarella_spi_finish_message(struct ambarella_spi *priv) |
| { |
| struct spi_message *msg; |
| unsigned long flags; |
| u32 message_pending; |
| |
| if (priv->chip_select) { |
| struct ambarella_spi_cs_config cs_config; |
| |
| cs_config.bus_id = priv->c_dev->master->bus_num; |
| cs_config.cs_id = priv->c_msg->spi->chip_select; |
| cs_config.cs_num = priv->c_dev->master->num_chipselect; |
| cs_config.cs_pins = priv->pinfo->cs_pins; |
| |
| priv->pinfo->cs_deactivate(&cs_config); |
| priv->chip_select = 0; |
| } |
| |
| msg = priv->c_msg; |
| msg->actual_length = priv->c_xfer->len; |
| msg->status = 0; |
| |
| /* Next Message */ |
| spin_lock_irqsave(&priv->lock, flags); |
| list_del_init(&msg->queue); |
| if (!list_empty(&priv->queue)) { |
| message_pending = 1; |
| } else { |
| message_pending = 0; |
| priv->idle = 1; |
| priv->c_msg = NULL; |
| priv->c_xfer = NULL; |
| } |
| spin_unlock_irqrestore(&priv->lock, flags); |
| |
| msg->complete(msg->context); |
| if (message_pending) { |
| ambarella_spi_handle_message(priv); |
| } |
| } |
| |
| static void ambarella_spi_handle_message(struct ambarella_spi *priv) |
| { |
| ambarella_spi_prepare_message(priv); |
| ambarella_spi_prepare_transfer(priv); |
| ambarella_spi_start_transfer(priv); |
| } |
| |
| static void ambarella_spi_prepare_message(struct ambarella_spi *priv) |
| { |
| u32 ctrlr0, ssi_clk, sckdv; |
| struct spi_message *msg; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| msg = list_entry(priv->queue.next, struct spi_message, queue); |
| spin_unlock_irqrestore(&priv->lock, flags); |
| ctrlr0 = amba_readl(priv->regbase + SPI_CTRLR0_OFFSET); |
| |
| if (msg->spi->bits_per_word < 4) |
| msg->spi->bits_per_word = 4; |
| if (msg->spi->bits_per_word > 16) |
| msg->spi->bits_per_word = 16; |
| priv->bpw = msg->spi->bits_per_word; |
| |
| ctrlr0 &= 0xfffffff0; |
| ctrlr0 |= (priv->bpw - 1); |
| |
| ctrlr0 &= (~((1 << 6) | (1 << 7))); |
| ctrlr0 |= ((msg->spi->mode & (SPI_CPHA | SPI_CPOL)) << 6); |
| if (msg->spi->mode & SPI_LOOP) { |
| ctrlr0 |= 0x00000800; |
| } |
| amba_writel(priv->regbase + SPI_CTRLR0_OFFSET, ctrlr0); |
| |
| ssi_clk = priv->pinfo->get_ssi_freq_hz(); |
| if(msg->spi->max_speed_hz == 0 || msg->spi->max_speed_hz > ssi_clk / 2) |
| msg->spi->max_speed_hz = ssi_clk / 2; |
| sckdv = (u16)(((ssi_clk / msg->spi->max_speed_hz) + 0x01) & 0xfffe); |
| amba_writel(priv->regbase + SPI_BAUDR_OFFSET, sckdv); |
| |
| priv->chip_select = 0; |
| priv->c_dev = msg->spi; |
| priv->c_msg = msg; |
| } |
| |
| static int ambarella_spi_main_entry(struct spi_device *spi, struct spi_message *msg) |
| { |
| struct ambarella_spi *priv; |
| struct spi_transfer *xfer; |
| unsigned long flags; |
| u32 shut_down, bus_idle; |
| |
| priv = spi_master_get_devdata(spi->master); |
| spin_lock_irqsave(&priv->lock, flags); |
| shut_down = priv->shutdown; |
| spin_unlock_irqrestore(&priv->lock, flags); |
| if (shut_down) { |
| return -ESHUTDOWN; |
| } |
| |
| /* Validation */ |
| if (list_empty(&msg->transfers) || !spi->max_speed_hz) { |
| return -EINVAL; |
| } |
| |
| list_for_each_entry(xfer, &msg->transfers, transfer_list) { |
| if (!xfer->tx_buf && !xfer->rx_buf) { |
| return -EINVAL; |
| } |
| |
| if (spi->bits_per_word > 8 && (xfer->len & 0x1)) { |
| return -EINVAL; |
| } |
| } |
| |
| /* Queue Message */ |
| msg->status = -EINPROGRESS; |
| msg->actual_length = 0; |
| spin_lock_irqsave(&priv->lock, flags); |
| list_add_tail(&msg->queue, &priv->queue); |
| if (priv->idle) { |
| priv->idle = 0; |
| bus_idle = 1; |
| } else { |
| bus_idle = 0; |
| } |
| spin_unlock_irqrestore(&priv->lock, flags); |
| |
| /* Handle message right away if bus is idle */ |
| if (bus_idle) { |
| ambarella_spi_handle_message(priv); |
| } |
| |
| return 0; |
| } |
| |
| static void ambarella_spi_cleanup(struct spi_device *spi) |
| { |
| return; |
| } |
| |
| static int ambarella_spi_inithw(struct ambarella_spi *priv) |
| { |
| u16 sckdv, i; |
| u32 ctrlr0, ssi_freq; |
| |
| /* Set PLL */ |
| if (priv->pinfo->rct_set_ssi_pll) |
| priv->pinfo->rct_set_ssi_pll(); |
| |
| /* Disable SPI */ |
| ambarella_spi_stop(priv); |
| |
| /* Config Chip Select Pins */ |
| for (i = 0; i < priv->pinfo->cs_num; i++) { |
| if (priv->pinfo->cs_pins[i] < 0) { |
| continue; |
| } |
| |
| ambarella_gpio_config(priv->pinfo->cs_pins[i], GPIO_FUNC_SW_OUTPUT); |
| ambarella_gpio_set(priv->pinfo->cs_pins[i], GPIO_HIGH); |
| } |
| |
| /* Initial Register Settings */ |
| ctrlr0 = ( ( SPI_CFS << 12) | (SPI_WRITE_ONLY << 8) | (SPI_SCPOL << 7) | |
| (SPI_SCPH << 6) | (SPI_FRF << 4) | (SPI_DFS) |
| ); |
| amba_writel(priv->regbase + SPI_CTRLR0_OFFSET, ctrlr0); |
| |
| ssi_freq = 54000000; |
| if (priv->pinfo->get_ssi_freq_hz) |
| ssi_freq = priv->pinfo->get_ssi_freq_hz(); |
| sckdv = (u16)(((ssi_freq / SPI_BAUD_RATE) + 0x01) & 0xfffe); |
| amba_writel(priv->regbase + SPI_BAUDR_OFFSET, sckdv); |
| |
| amba_writel(priv->regbase + SPI_TXFTLR_OFFSET, 0); |
| amba_writel(priv->regbase + SPI_RXFTLR_OFFSET, 1); |
| |
| return 0; |
| } |
| |
| static irqreturn_t ambarella_spi_isr(int irq, void *dev_data) |
| { |
| struct ambarella_spi *priv = dev_data; |
| |
| if (amba_readl(priv->regbase + SPI_ISR_OFFSET)) { |
| disable_irq_nosync(priv->irq); |
| amba_writel(priv->regbase + SPI_ISR_OFFSET, 0); |
| |
| ambarella_spi_tasklet((unsigned long)priv); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int __devinit ambarella_spi_probe(struct platform_device *pdev) |
| { |
| struct ambarella_spi *priv; |
| struct ambarella_spi_private *ps; |
| struct spi_master *master; |
| struct spi_device *spidev; |
| struct resource *res; |
| struct ambarella_spi_platform_info *pinfo; |
| int i, irq, errorCode; |
| |
| /* Get IRQ NO. */ |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) { |
| errorCode = -EINVAL; |
| goto ambarella_spi_probe_exit3; |
| } |
| |
| /* Get Platform Info */ |
| pinfo = (struct ambarella_spi_platform_info *)pdev->dev.platform_data; |
| if (!pinfo) { |
| errorCode = -EINVAL; |
| goto ambarella_spi_probe_exit3; |
| } |
| if (pinfo->cs_num && !pinfo->cs_pins) { |
| errorCode = -EINVAL; |
| goto ambarella_spi_probe_exit3; |
| } |
| if (!pinfo->cs_activate || !pinfo->cs_deactivate) { |
| errorCode = -EINVAL; |
| goto ambarella_spi_probe_exit3; |
| } |
| if (!pinfo->rct_set_ssi_pll || !pinfo->get_ssi_freq_hz) { |
| errorCode = -EINVAL; |
| goto ambarella_spi_probe_exit3; |
| } |
| |
| /* Get Base Address */ |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| errorCode = -EINVAL; |
| goto ambarella_spi_probe_exit3; |
| } |
| |
| /* Alocate Master */ |
| master = spi_alloc_master(&pdev->dev, sizeof *priv); |
| if (!master) { |
| errorCode = -ENOMEM; |
| goto ambarella_spi_probe_exit3; |
| } |
| |
| /* Initalize Device Data */ |
| master->bus_num = pdev->id; |
| master->num_chipselect = pinfo->cs_num; |
| master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST | SPI_LOOP; |
| master->setup = ambarella_spi_setup; |
| master->transfer = ambarella_spi_main_entry; |
| master->cleanup = ambarella_spi_cleanup; |
| platform_set_drvdata(pdev, master); |
| priv = spi_master_get_devdata(master); |
| priv->regbase = (u32)res->start; |
| priv->irq = irq; |
| priv->pinfo = pinfo; |
| tasklet_init(&priv->tasklet, ambarella_spi_tasklet, (unsigned long)priv); |
| INIT_LIST_HEAD(&priv->queue); |
| priv->idle = 1; |
| priv->c_dev = NULL; |
| priv->c_msg = NULL; |
| priv->c_xfer = NULL; |
| priv->shutdown = 0; |
| spin_lock_init(&priv->lock); |
| priv->bpw = 16; |
| |
| /* Inittialize Hardware*/ |
| ambarella_spi_inithw(priv); |
| |
| /* Request IRQ */ |
| errorCode = request_irq(irq, ambarella_spi_isr, IRQF_TRIGGER_HIGH, |
| dev_name(&pdev->dev), priv); |
| if (errorCode) |
| goto ambarella_spi_probe_exit2; |
| else |
| dev_info(&pdev->dev, "ambarella SPI Controller %d created \n", pdev->id); |
| |
| /* Register Master */ |
| errorCode = spi_register_master(master); |
| if (errorCode) |
| goto ambarella_spi_probe_exit1; |
| |
| /* Allocate Private Devices */ |
| ps = (struct ambarella_spi_private *)kmalloc(master->num_chipselect * \ |
| sizeof(struct ambarella_spi_private), GFP_KERNEL); |
| if (!ps) { |
| errorCode = -ENOMEM; |
| goto ambarella_spi_probe_exit3; |
| } |
| spidev = (struct spi_device *)kmalloc(master->num_chipselect * \ |
| sizeof(struct spi_device), GFP_KERNEL); |
| if (!spidev) { |
| errorCode = -ENOMEM; |
| kfree(ps); |
| goto ambarella_spi_probe_exit3; |
| } |
| |
| for (i = 0; i < master->num_chipselect; i++) { |
| ps[i].spi = spidev + i; |
| ps[i].spi->master = master; |
| mutex_init(&ps[i].mtx); |
| spin_lock_init(&ps[i].lock); |
| } |
| ambarella_spi_private_devices[master->bus_num].cs_num = master->num_chipselect; |
| ambarella_spi_private_devices[master->bus_num].data = ps; |
| |
| goto ambarella_spi_probe_exit3; |
| |
| ambarella_spi_probe_exit1: |
| free_irq(irq, priv); |
| |
| ambarella_spi_probe_exit2: |
| tasklet_kill(&priv->tasklet); |
| spi_master_put(master); |
| |
| ambarella_spi_probe_exit3: |
| return errorCode; |
| } |
| |
| static int __devexit ambarella_spi_remove(struct platform_device *pdev) |
| { |
| |
| struct spi_master *master = platform_get_drvdata(pdev); |
| struct ambarella_spi *priv = spi_master_get_devdata(master); |
| struct spi_message *msg; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| priv->shutdown = 1; |
| spin_unlock_irqrestore(&priv->lock, flags); |
| tasklet_kill(&priv->tasklet); |
| free_irq(priv->irq, priv); |
| ambarella_spi_stop(priv); |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| list_for_each_entry(msg, &priv->queue, queue) { |
| msg->status = -ESHUTDOWN; |
| msg->complete(msg->context); |
| } |
| spin_unlock_irqrestore(&priv->lock, flags); |
| |
| spi_unregister_master(master); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int ambarella_spi_suspend_noirq(struct device *dev) |
| { |
| int errorCode = 0; |
| struct spi_master *master; |
| struct ambarella_spi *priv; |
| struct platform_device *pdev; |
| |
| pdev = to_platform_device(dev); |
| master = platform_get_drvdata(pdev); |
| priv = spi_master_get_devdata(master); |
| |
| if (priv) { |
| //disable_irq(priv->irq); |
| ambarella_spi_stop(priv); |
| } else { |
| dev_err(&pdev->dev, "Cannot find valid pinfo\n"); |
| errorCode = -ENXIO; |
| } |
| |
| dev_dbg(&pdev->dev, "%s\n", __func__); |
| |
| return errorCode; |
| } |
| |
| static int ambarella_spi_resume_noirq(struct device *dev) |
| { |
| int errorCode = 0; |
| struct spi_master *master; |
| struct ambarella_spi *priv; |
| struct platform_device *pdev; |
| |
| pdev = to_platform_device(dev); |
| master = platform_get_drvdata(pdev); |
| priv = spi_master_get_devdata(master); |
| if (priv) { |
| ambarella_spi_inithw(priv); |
| //enable_irq(priv->irq); |
| } else { |
| dev_err(&pdev->dev, "Cannot find valid pinfo\n"); |
| errorCode = -ENXIO; |
| } |
| |
| dev_dbg(&pdev->dev, "%s\n", __func__); |
| |
| return errorCode; |
| } |
| |
| static const struct dev_pm_ops ambarella_spi_dev_pm_ops = { |
| .suspend_noirq = ambarella_spi_suspend_noirq, |
| .resume_noirq = ambarella_spi_resume_noirq, |
| }; |
| #endif |
| |
| static struct platform_driver ambarella_spi_driver = { |
| .probe = ambarella_spi_probe, |
| .remove = __devexit_p(ambarella_spi_remove), |
| .driver = { |
| .name = "ambarella-spi", |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_PM |
| .pm = &ambarella_spi_dev_pm_ops, |
| #endif |
| }, |
| }; |
| |
| static int __init ambarella_spi_init(void) |
| { |
| return platform_driver_register(&ambarella_spi_driver); |
| } |
| |
| static void __exit ambarella_spi_exit(void) |
| { |
| platform_driver_unregister(&ambarella_spi_driver); |
| } |
| |
| subsys_initcall(ambarella_spi_init); |
| module_exit(ambarella_spi_exit); |
| |
| MODULE_DESCRIPTION("Ambarella Media Processor SPI Bus Controller"); |
| MODULE_AUTHOR("Louis Sun, <louis.sun@ambarella.com>"); |
| MODULE_LICENSE("GPL"); |
| |
| |
| /*=================Utilities for Non-GPL Use==================================*/ |
| static void ambarella_spi_complete(void *arg) |
| { |
| complete(arg); |
| } |
| |
| int ambarella_spi_write(amba_spi_cfg_t *spi_cfg, amba_spi_write_t *spi_write) |
| { |
| u8 bus_id, cs_id, cs_num; |
| unsigned long flags; |
| int errorCode; |
| struct ambarella_spi_private *ps; |
| struct spi_device *spi; |
| struct spi_message msg; |
| struct spi_transfer xfer; |
| |
| DECLARE_COMPLETION_ONSTACK(done); |
| |
| /* Validate Input Args */ |
| if (!spi_cfg || !spi_write) |
| return -EINVAL; |
| |
| bus_id = spi_write->bus_id; |
| cs_id = spi_write->cs_id; |
| cs_num = ambarella_spi_private_devices[bus_id].cs_num; |
| ps = ambarella_spi_private_devices[bus_id].data; |
| |
| if (bus_id >= SPI_MASTER_INSTANCES || cs_id >= cs_num |
| || !spi_write->buffer || !spi_write->n_size) |
| return -EINVAL; |
| |
| /* Transfer */ |
| memset(&xfer, 0, sizeof(struct spi_transfer)); |
| xfer.tx_buf = spi_write->buffer; |
| xfer.len = spi_write->n_size; |
| xfer.cs_change = spi_cfg->cs_change; |
| |
| /* Message */ |
| memset(&msg, 0, sizeof(struct spi_message)); |
| INIT_LIST_HEAD(&msg.transfers); |
| list_add_tail(&xfer.transfer_list, &msg.transfers); |
| msg.complete = ambarella_spi_complete; |
| msg.context = &done; |
| spi = ps[cs_id].spi; |
| msg.spi = spi; |
| |
| mutex_lock(&ps[cs_id].mtx); |
| |
| /* Config */ |
| spi->chip_select = cs_id; |
| spi->mode = spi_cfg->spi_mode; |
| spi->mode &= ~SPI_LOOP; |
| spi->bits_per_word = spi_cfg->cfs_dfs; |
| spi->max_speed_hz = spi_cfg->baud_rate; |
| |
| /* Wait */ |
| spin_lock_irqsave(&ps[cs_id].lock, flags); |
| errorCode = spi->master->transfer(spi, &msg); |
| spin_unlock_irqrestore(&ps[cs_id].lock, flags); |
| if (!errorCode) |
| wait_for_completion(&done); |
| |
| mutex_unlock(&ps[cs_id].mtx); |
| |
| return errorCode; |
| } |
| EXPORT_SYMBOL(ambarella_spi_write); |
| |
| int ambarella_spi_read(amba_spi_cfg_t *spi_cfg, amba_spi_read_t *spi_read) |
| { |
| u8 bus_id, cs_id, cs_num; |
| unsigned long flags; |
| int errorCode; |
| struct ambarella_spi_private *ps; |
| struct spi_device *spi; |
| struct spi_message msg; |
| struct spi_transfer xfer; |
| |
| DECLARE_COMPLETION_ONSTACK(done); |
| |
| /* Validate Input Args */ |
| if (!spi_cfg || !spi_read) |
| return -EINVAL; |
| |
| bus_id = spi_read->bus_id; |
| cs_id = spi_read->cs_id; |
| cs_num = ambarella_spi_private_devices[bus_id].cs_num; |
| ps = ambarella_spi_private_devices[bus_id].data; |
| |
| if (bus_id >= SPI_MASTER_INSTANCES || cs_id >= cs_num |
| || !spi_read->buffer || !spi_read->n_size) |
| return -EINVAL; |
| |
| /* Transfer */ |
| memset(&xfer, 0, sizeof(struct spi_transfer)); |
| xfer.rx_buf = spi_read->buffer; |
| xfer.len = spi_read->n_size; |
| xfer.cs_change = spi_cfg->cs_change; |
| |
| /* Message */ |
| memset(&msg, 0, sizeof(struct spi_message)); |
| INIT_LIST_HEAD(&msg.transfers); |
| list_add_tail(&xfer.transfer_list, &msg.transfers); |
| msg.complete = ambarella_spi_complete; |
| msg.context = &done; |
| spi = ps[cs_id].spi; |
| msg.spi = spi; |
| |
| mutex_lock(&ps[cs_id].mtx); |
| |
| /* Config */ |
| spi->chip_select = cs_id; |
| spi->mode = spi_cfg->spi_mode; |
| spi->mode &= ~SPI_LOOP; |
| spi->bits_per_word = spi_cfg->cfs_dfs; |
| spi->max_speed_hz = spi_cfg->baud_rate; |
| |
| /* Wait */ |
| spin_lock_irqsave(&ps[cs_id].lock, flags); |
| errorCode = spi->master->transfer(spi, &msg); |
| spin_unlock_irqrestore(&ps[cs_id].lock, flags); |
| if (!errorCode) |
| wait_for_completion(&done); |
| |
| mutex_unlock(&ps[cs_id].mtx); |
| |
| return errorCode; |
| } |
| EXPORT_SYMBOL(ambarella_spi_read); |
| |
| int ambarella_spi_write_then_read(amba_spi_cfg_t *spi_cfg, |
| amba_spi_write_then_read_t *spi_write_then_read) |
| { |
| u8 bus_id, cs_id, cs_num, *buf; |
| u16 size; |
| unsigned long flags; |
| int errorCode; |
| struct ambarella_spi_private *ps; |
| struct spi_device *spi; |
| struct spi_message msg; |
| struct spi_transfer xfer; |
| |
| DECLARE_COMPLETION_ONSTACK(done); |
| |
| /* Validate Input Args */ |
| if (!spi_cfg || !spi_write_then_read) |
| return -EINVAL; |
| |
| bus_id = spi_write_then_read->bus_id; |
| cs_id = spi_write_then_read->cs_id; |
| cs_num = ambarella_spi_private_devices[bus_id].cs_num; |
| ps = ambarella_spi_private_devices[bus_id].data; |
| |
| if (bus_id >= SPI_MASTER_INSTANCES || cs_id >= cs_num |
| || !spi_write_then_read->w_buffer || !spi_write_then_read->w_size |
| || !spi_write_then_read->r_buffer || !spi_write_then_read->r_size) |
| return -EINVAL; |
| |
| /* Prepare Buffer */ |
| size = spi_write_then_read->w_size + spi_write_then_read->r_size; |
| buf = (u8 *)kmalloc(size, GFP_KERNEL); |
| if (!buf) |
| return -ENOMEM; |
| |
| memcpy(buf, spi_write_then_read->w_buffer, spi_write_then_read->w_size); |
| memset(buf + spi_write_then_read->w_size, SPI_DUMMY_DATA, |
| spi_write_then_read->r_size); |
| |
| /* Transfer */ |
| memset(&xfer, 0, sizeof(struct spi_transfer)); |
| xfer.tx_buf = buf; |
| xfer.rx_buf = buf; |
| xfer.len = size; |
| xfer.cs_change = spi_cfg->cs_change; |
| |
| /* Message */ |
| memset(&msg, 0, sizeof(struct spi_message)); |
| INIT_LIST_HEAD(&msg.transfers); |
| list_add_tail(&xfer.transfer_list, &msg.transfers); |
| msg.complete = ambarella_spi_complete; |
| msg.context = &done; |
| spi = ps[cs_id].spi; |
| msg.spi = spi; |
| |
| mutex_lock(&ps[cs_id].mtx); |
| |
| /* Config */ |
| spi->chip_select = cs_id; |
| spi->mode = spi_cfg->spi_mode; |
| spi->mode &= ~SPI_LOOP; |
| spi->bits_per_word = spi_cfg->cfs_dfs; |
| spi->max_speed_hz = spi_cfg->baud_rate; |
| |
| /* Wait */ |
| spin_lock_irqsave(&ps[cs_id].lock, flags); |
| errorCode = spi->master->transfer(spi, &msg); |
| spin_unlock_irqrestore(&ps[cs_id].lock, flags); |
| if (!errorCode) |
| wait_for_completion(&done); |
| |
| mutex_unlock(&ps[cs_id].mtx); |
| |
| /* Free Buffer */ |
| memcpy(spi_write_then_read->r_buffer, buf + spi_write_then_read->w_size, |
| spi_write_then_read->r_size); |
| kfree(buf); |
| |
| return errorCode; |
| } |
| 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_write_and_read) |
| { |
| u8 bus_id, cs_id, cs_num; |
| unsigned long flags; |
| int errorCode; |
| struct ambarella_spi_private *ps; |
| struct spi_device *spi; |
| struct spi_message msg; |
| struct spi_transfer xfer; |
| |
| DECLARE_COMPLETION_ONSTACK(done); |
| |
| /* Validate Input Args */ |
| if (!spi_cfg || !spi_write_and_read) |
| return -EINVAL; |
| |
| bus_id = spi_write_and_read->bus_id; |
| cs_id = spi_write_and_read->cs_id; |
| cs_num = ambarella_spi_private_devices[bus_id].cs_num; |
| ps = ambarella_spi_private_devices[bus_id].data; |
| |
| if (bus_id >= SPI_MASTER_INSTANCES || cs_id >= cs_num |
| || !spi_write_and_read->w_buffer|| !spi_write_and_read->r_buffer |
| || !spi_write_and_read->n_size) |
| return -EINVAL; |
| |
| /* Transfer */ |
| memset(&xfer, 0, sizeof(struct spi_transfer)); |
| xfer.tx_buf = spi_write_and_read->w_buffer; |
| xfer.rx_buf = spi_write_and_read->r_buffer; |
| xfer.len = spi_write_and_read->n_size; |
| xfer.cs_change = spi_cfg->cs_change; |
| |
| /* Message */ |
| memset(&msg, 0, sizeof(struct spi_message)); |
| INIT_LIST_HEAD(&msg.transfers); |
| list_add_tail(&xfer.transfer_list, &msg.transfers); |
| msg.complete = ambarella_spi_complete; |
| msg.context = &done; |
| spi = ps[cs_id].spi; |
| msg.spi = spi; |
| |
| mutex_lock(&ps[cs_id].mtx); |
| |
| /* Config */ |
| spi->chip_select = cs_id; |
| spi->mode = spi_cfg->spi_mode; |
| spi->mode &= ~SPI_LOOP; |
| spi->bits_per_word = spi_cfg->cfs_dfs; |
| spi->max_speed_hz = spi_cfg->baud_rate; |
| |
| /* Wait */ |
| spin_lock_irqsave(&ps[cs_id].lock, flags); |
| errorCode = spi->master->transfer(spi, &msg); |
| spin_unlock_irqrestore(&ps[cs_id].lock, flags); |
| if (!errorCode) |
| wait_for_completion(&done); |
| |
| mutex_unlock(&ps[cs_id].mtx); |
| |
| return errorCode; |
| } |
| EXPORT_SYMBOL(ambarella_spi_write_and_read); |
| |