/* * Sequencer Serial Port (SSP) based SPI master driver * * Copyright (C) 2010 Texas Instruments 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 #include #include #include #include #include #include #define MODE_BITS (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH) struct ti_ssp_spi { struct spi_master *master; struct device *dev; spinlock_t lock; struct list_head msg_queue; struct completion complete; bool shutdown; struct workqueue_struct *workqueue; struct work_struct work; u8 mode, bpw; int cs_active; u32 pc_en, pc_dis, pc_wr, pc_rd; void (*select)(int cs); }; static u32 ti_ssp_spi_rx(struct ti_ssp_spi *hw) { u32 ret; ti_ssp_run(hw->dev, hw->pc_rd, 0, &ret); return ret; } static void ti_ssp_spi_tx(struct ti_ssp_spi *hw, u32 data) { ti_ssp_run(hw->dev, hw->pc_wr, data << (32 - hw->bpw), NULL); } static int ti_ssp_spi_txrx(struct ti_ssp_spi *hw, struct spi_message *msg, struct spi_transfer *t) { int count; if (hw->bpw <= 8) { u8 *rx = t->rx_buf; const u8 *tx = t->tx_buf; for (count = 0; count < t->len; count += 1) { if (t->tx_buf) ti_ssp_spi_tx(hw, *tx++); if (t->rx_buf) *rx++ = ti_ssp_spi_rx(hw); } } else if (hw->bpw <= 16) { u16 *rx = t->rx_buf; const u16 *tx = t->tx_buf; for (count = 0; count < t->len; count += 2) { if (t->tx_buf) ti_ssp_spi_tx(hw, *tx++); if (t->rx_buf) *rx++ = ti_ssp_spi_rx(hw); } } else { u32 *rx = t->rx_buf; const u32 *tx = t->tx_buf; for (count = 0; count < t->len; count += 4) { if (t->tx_buf) ti_ssp_spi_tx(hw, *tx++); if (t->rx_buf) *rx++ = ti_ssp_spi_rx(hw); } } msg->actual_length += count; /* bytes transferred */ dev_dbg(&msg->spi->dev, "xfer %s%s, %d bytes, %d bpw, count %d%s\n", t->tx_buf ? "tx" : "", t->rx_buf ? "rx" : "", t->len, hw->bpw, count, (count < t->len) ? " (under)" : ""); return (count < t->len) ? -EIO : 0; /* left over data */ } static void ti_ssp_spi_chip_select(struct ti_ssp_spi *hw, int cs_active) { cs_active = !!cs_active; if (cs_active == hw->cs_active) return; ti_ssp_run(hw->dev, cs_active ? hw->pc_en : hw->pc_dis, 0, NULL); hw->cs_active = cs_active; } #define __SHIFT_OUT(bits) (SSP_OPCODE_SHIFT | SSP_OUT_MODE | \ cs_en | clk | SSP_COUNT((bits) * 2 - 1)) #define __SHIFT_IN(bits) (SSP_OPCODE_SHIFT | SSP_IN_MODE | \ cs_en | clk | SSP_COUNT((bits) * 2 - 1)) static int ti_ssp_spi_setup_transfer(struct ti_ssp_spi *hw, u8 bpw, u8 mode) { int error, idx = 0; u32 seqram[16]; u32 cs_en, cs_dis, clk; u32 topbits, botbits; mode &= MODE_BITS; if (mode == hw->mode && bpw == hw->bpw) return 0; cs_en = (mode & SPI_CS_HIGH) ? SSP_CS_HIGH : SSP_CS_LOW; cs_dis = (mode & SPI_CS_HIGH) ? SSP_CS_LOW : SSP_CS_HIGH; clk = (mode & SPI_CPOL) ? SSP_CLK_HIGH : SSP_CLK_LOW; /* Construct instructions */ /* Disable Chip Select */ hw->pc_dis = idx; seqram[idx++] = SSP_OPCODE_DIRECT | SSP_OUT_MODE | cs_dis | clk; seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_dis | clk; /* Enable Chip Select */ hw->pc_en = idx; seqram[idx++] = SSP_OPCODE_DIRECT | SSP_OUT_MODE | cs_en | clk; seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk; /* Reads and writes need to be split for bpw > 16 */ topbits = (bpw > 16) ? 16 : bpw; botbits = bpw - topbits; /* Write */ hw->pc_wr = idx; seqram[idx++] = __SHIFT_OUT(topbits) | SSP_ADDR_REG; if (botbits) seqram[idx++] = __SHIFT_OUT(botbits) | SSP_DATA_REG; seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk; /* Read */ hw->pc_rd = idx; if (botbits) seqram[idx++] = __SHIFT_IN(botbits) | SSP_ADDR_REG; seqram[idx++] = __SHIFT_IN(topbits) | SSP_DATA_REG; seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk; error = ti_ssp_load(hw->dev, 0, seqram, idx); if (error < 0) return error; error = ti_ssp_set_mode(hw->dev, ((mode & SPI_CPHA) ? 0 : SSP_EARLY_DIN)); if (error < 0) return error; hw->bpw = bpw; hw->mode = mode; return error; } static void ti_ssp_spi_work(struct work_struct *work) { struct ti_ssp_spi *hw = container_of(work, struct ti_ssp_spi, work); spin_lock(&hw->lock); while (!list_empty(&hw->msg_queue)) { struct spi_message *m; struct spi_device *spi; struct spi_transfer *t = NULL; int status = 0; m = container_of(hw->msg_queue.next, struct spi_message, queue); list_del_init(&m->queue); spin_unlock(&hw->lock); spi = m->spi; if (hw->select) hw->select(spi->chip_select); list_for_each_entry(t, &m->transfers, transfer_list) { int bpw = spi->bits_per_word; int xfer_status; if (t->bits_per_word) bpw = t->bits_per_word; if (ti_ssp_spi_setup_transfer(hw, bpw, spi->mode) < 0) break; ti_ssp_spi_chip_select(hw, 1); xfer_status = ti_ssp_spi_txrx(hw, m, t); if (xfer_status < 0) status = xfer_status; if (t->delay_usecs) udelay(t->delay_usecs); if (t->cs_change) ti_ssp_spi_chip_select(hw, 0); } ti_ssp_spi_chip_select(hw, 0); m->status = status; m->complete(m->context); spin_lock(&hw->lock); } if (hw->shutdown) complete(&hw->complete); spin_unlock(&hw->lock); } static int ti_ssp_spi_setup(struct spi_device *spi) { if (spi->bits_per_word > 32) return -EINVAL; return 0; } static int ti_ssp_spi_transfer(struct spi_device *spi, struct spi_message *m) { struct ti_ssp_spi *hw; struct spi_transfer *t; int error = 0; m->actual_length = 0; m->status = -EINPROGRESS; hw = spi_master_get_devdata(spi->master); if (list_empty(&m->transfers) || !m->complete) return -EINVAL; list_for_each_entry(t, &m->transfers, transfer_list) { if (t->len && !(t->rx_buf || t->tx_buf)) { dev_err(&spi->dev, "invalid xfer, no buffer\n"); return -EINVAL; } if (t->len && t->rx_buf && t->tx_buf) { dev_err(&spi->dev, "invalid xfer, full duplex\n"); return -EINVAL; } if (t->bits_per_word > 32) { dev_err(&spi->dev, "invalid xfer width %d\n", t->bits_per_word); return -EINVAL; } } spin_lock(&hw->lock); if (hw->shutdown) { error = -ESHUTDOWN; goto error_unlock; } list_add_tail(&m->queue, &hw->msg_queue); queue_work(hw->workqueue, &hw->work); error_unlock: spin_unlock(&hw->lock); return error; } static int __devinit ti_ssp_spi_probe(struct platform_device *pdev) { const struct ti_ssp_spi_data *pdata; struct ti_ssp_spi *hw; struct spi_master *master; struct device *dev = &pdev->dev; int error = 0; pdata = dev->platform_data; if (!pdata) { dev_err(dev, "platform data not found\n"); return -EINVAL; } master = spi_alloc_master(dev, sizeof(struct ti_ssp_spi)); if (!master) { dev_err(dev, "cannot allocate SPI master\n"); return -ENOMEM; } hw = spi_master_get_devdata(master); platform_set_drvdata(pdev, hw); hw->master = master; hw->dev = dev; hw->select = pdata->select; spin_lock_init(&hw->lock); init_completion(&hw->complete); INIT_LIST_HEAD(&hw->msg_queue); INIT_WORK(&hw->work, ti_ssp_spi_work); hw->workqueue = create_singlethread_workqueue(dev_name(dev)); if (!hw->workqueue) { error = -ENOMEM; dev_err(dev, "work queue creation failed\n"); goto error_wq; } error = ti_ssp_set_iosel(hw->dev, pdata->iosel); if (error < 0) { dev_err(dev, "io setup failed\n"); goto error_iosel; } master->bus_num = pdev->id; master->num_chipselect = pdata->num_cs; master->mode_bits = MODE_BITS; master->flags = SPI_MASTER_HALF_DUPLEX; master->setup = ti_ssp_spi_setup; master->transfer = ti_ssp_spi_transfer; error = spi_register_master(master); if (error) { dev_err(dev, "master registration failed\n"); goto error_reg; } return 0; error_reg: error_iosel: destroy_workqueue(hw->workqueue); error_wq: spi_master_put(master); return error; } static int __devexit ti_ssp_spi_remove(struct platform_device *pdev) { struct ti_ssp_spi *hw = platform_get_drvdata(pdev); int error; hw->shutdown = 1; while (!list_empty(&hw->msg_queue)) { error = wait_for_completion_interruptible(&hw->complete); if (error < 0) { hw->shutdown = 0; return error; } } destroy_workqueue(hw->workqueue); spi_unregister_master(hw->master); return 0; } static struct platform_driver ti_ssp_spi_driver = { .probe = ti_ssp_spi_probe, .remove = __devexit_p(ti_ssp_spi_remove), .driver = { .name = "ti-ssp-spi", .owner = THIS_MODULE, }, }; static int __init ti_ssp_spi_init(void) { return platform_driver_register(&ti_ssp_spi_driver); } module_init(ti_ssp_spi_init); static void __exit ti_ssp_spi_exit(void) { platform_driver_unregister(&ti_ssp_spi_driver); } module_exit(ti_ssp_spi_exit); MODULE_DESCRIPTION("SSP SPI Master"); MODULE_AUTHOR("Cyril Chemparathy"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:ti-ssp-spi");