diff options
Diffstat (limited to 'meta-steppeeagle/recipes-kernel/amd-spi/files/spi_amd.c')
-rw-r--r-- | meta-steppeeagle/recipes-kernel/amd-spi/files/spi_amd.c | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/meta-steppeeagle/recipes-kernel/amd-spi/files/spi_amd.c b/meta-steppeeagle/recipes-kernel/amd-spi/files/spi_amd.c new file mode 100644 index 00000000..554bd352 --- /dev/null +++ b/meta-steppeeagle/recipes-kernel/amd-spi/files/spi_amd.c @@ -0,0 +1,492 @@ +/***************************************************************************** +* +* Copyright (c) 2014, Advanced Micro Devices, Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of Advanced Micro Devices, Inc. nor the names of +* its contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL ADVANCED MICRO DEVICES, INC. BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* +***************************************************************************/ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/spi/spi.h> +#include <linux/kthread.h> + +#include "spi_amd.h" + +struct amd_platform_data { + u8 chip_select; +}; + +struct amd_spi { + void __iomem *io_remap_addr; + unsigned long io_base_addr; + u32 rom_addr; + struct spi_master *master; + struct amd_platform_data controller_data; + spinlock_t lock; + struct task_struct *kthread_spi; + struct list_head msg_queue; +}; + +static struct pci_device_id amd_spi_pci_device_id[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LPC_BRIDGE) }, + {} +}; +MODULE_DEVICE_TABLE(pci, amd_spi_pci_device_id); + +static inline u8 amd_spi_readreg8(struct spi_master *master, int idx) +{ + struct amd_spi *amd_spi = spi_master_get_devdata(master); + + return ioread8((u8 *)amd_spi->io_remap_addr + idx); +} + +static inline void amd_spi_writereg8(struct spi_master *master, int idx, + u8 val) +{ + struct amd_spi *amd_spi = spi_master_get_devdata(master); + + iowrite8(val, ((u8 *)amd_spi->io_remap_addr + idx)); +} + +static inline void amd_spi_setclear_reg8(struct spi_master *master, int idx, + u8 set, u8 clear) +{ + u8 tmp = amd_spi_readreg8(master, idx); + tmp = (tmp & ~clear) | set; + amd_spi_writereg8(master, idx, tmp); +} + +static inline u32 amd_spi_readreg32(struct spi_master *master, int idx) +{ + struct amd_spi *amd_spi = spi_master_get_devdata(master); + + return ioread32((u8 *)amd_spi->io_remap_addr + idx); +} + +static inline void amd_spi_writereg32(struct spi_master *master, int idx, + u32 val) +{ + struct amd_spi *amd_spi = spi_master_get_devdata(master); + + iowrite32(val, ((u8 *)amd_spi->io_remap_addr + idx)); +} + +static inline void amd_spi_setclear_reg32(struct spi_master *master, int idx, + u32 set, u32 clear) +{ + u32 tmp = amd_spi_readreg32(master, idx); + tmp = (tmp & ~clear) | set; + amd_spi_writereg32(master, idx, tmp); +} + +static void amd_spi_select_chip(struct spi_master *master) +{ + struct amd_spi *amd_spi = spi_master_get_devdata(master); + u8 chip_select = amd_spi->controller_data.chip_select; + + amd_spi_setclear_reg8(master, AMD_SPI_ALT_CS_REG, chip_select, + AMD_SPI_ALT_CS_MASK); +} + + +static void amd_spi_clear_fifo_ptr(struct spi_master *master) +{ + amd_spi_setclear_reg32(master, AMD_SPI_CTRL0_REG, AMD_SPI_FIFO_CLEAR, + AMD_SPI_FIFO_CLEAR); +} + +static void amd_spi_set_opcode(struct spi_master *master, u8 cmd_opcode) +{ + amd_spi_setclear_reg32(master, AMD_SPI_CTRL0_REG, cmd_opcode, + AMD_SPI_OPCODE_MASK); +} + +static inline void amd_spi_set_rx_count(struct spi_master *master, + u8 rx_count) +{ + amd_spi_setclear_reg8(master, AMD_SPI_RX_COUNT_REG, rx_count, 0xff); +} + +static inline void amd_spi_set_tx_count(struct spi_master *master, + u8 tx_count) +{ + amd_spi_setclear_reg8(master, AMD_SPI_TX_COUNT_REG, tx_count, 0xff); +} + +static void amd_spi_execute_opcode(struct spi_master *master) +{ + struct amd_spi *amd_spi = spi_master_get_devdata(master); + bool spi_busy; + + /* Set ExecuteOpCode bit in the CTRL0 register */ + amd_spi_setclear_reg32(master, AMD_SPI_CTRL0_REG, AMD_SPI_EXEC_CMD, + AMD_SPI_EXEC_CMD); + + /* poll for SPI bus to become idle */ + spi_busy = (ioread32((u8 *)amd_spi->io_remap_addr + + AMD_SPI_CTRL0_REG) & AMD_SPI_BUSY) == AMD_SPI_BUSY; + while (spi_busy) { + schedule(); + spi_busy = (ioread32((u8 *)amd_spi->io_remap_addr + + AMD_SPI_CTRL0_REG) & AMD_SPI_BUSY) == AMD_SPI_BUSY; + } +} + +/* Helper function */ +#ifdef CONFIG_SPI_DEBUG +static void amd_spi_dump_reg(struct spi_master *master) +{ + struct amd_spi *amd_spi = spi_master_get_devdata(master); + + printk(KERN_DEBUG DRIVER_NAME ": SPI CTRL 0 registers: 0x%.8x\n", + ioread32((u8 *)amd_spi->io_remap_addr + AMD_SPI_CTRL0_REG)); + /* + * We cannot read CTRL1 register, because reading it would + * inadvertently increment the FIFO pointer. + */ + printk(KERN_DEBUG DRIVER_NAME ": SPI ALT CS registers: 0x%.2x\n", + ioread8((u8 *)amd_spi->io_remap_addr + AMD_SPI_ALT_CS_REG)); + printk(KERN_DEBUG DRIVER_NAME ": SPI Tx Byte Count: 0x%.2x\n", + ioread8((u8 *)amd_spi->io_remap_addr + AMD_SPI_TX_COUNT_REG)); + printk(KERN_DEBUG DRIVER_NAME ": SPI Rx Byte Count: 0x%.2x\n", + ioread8((u8 *)amd_spi->io_remap_addr + AMD_SPI_RX_COUNT_REG)); + printk(KERN_DEBUG DRIVER_NAME ": SPI Status registers: 0x%.8x\n", + ioread32((u8 *)amd_spi->io_remap_addr + AMD_SPI_STATUS_REG)); +} +#else +static void amd_spi_dump_reg(struct spi_master *master) {} +#endif + + +static int amd_spi_master_setup(struct spi_device *spi) +{ + struct spi_master *master = spi->master; + struct amd_spi *amd_spi = spi_master_get_devdata(master); + + amd_spi->controller_data.chip_select = spi->chip_select; + + amd_spi_select_chip(master); + + return 0; +} + +static int amd_spi_master_transfer(struct spi_device *spi, + struct spi_message *msg) +{ + struct spi_master *master = spi->master; + struct amd_spi *amd_spi = spi_master_get_devdata(master); + + /* + * We will just add this message to the message queue set up by + * the controller, and let the kernel thread handle it later. + */ + msg->status = -EINPROGRESS; + msg->actual_length = 0; + msg->spi = spi; + /* + * There could be a situation when we are running on this processor + * and trying to add element to the end of the message queue, but + * at the same time the kernel thread is running on another processor + * and trying to find out if the list is empty or not. So protect + * against such contention. Simple spin_lock() should do. + */ + spin_lock(&amd_spi->lock); + list_add_tail(&msg->queue, &amd_spi->msg_queue); + spin_unlock(&amd_spi->lock); + + return 0; +} +static int amd_spi_thread(void *t) +{ + struct amd_spi *amd_spi = t; + struct spi_master *master = amd_spi->master; + struct spi_transfer *transfer = NULL; + struct spi_message *message = NULL; + int direction = 0,i = 0,saved_index = 0; + int opcode_found = 0,recv_flag = 0,tx_len = 0,rx_len = 0; + u8 cmd_opcode = 0; + u8 *buffer = NULL; + + /* + * What we do here is actually pretty simple. We pick one message + * at a time from the message queue set up by the controller, and + * then process all the spi_transfers of that spi_message in one go. + * We then remove the message from the queue, and complete the + * transaction. This might not be the best approach, but this is how + * we chose to implement this. Note that out SPI controller has FIFO + * size of 70 bytes, but we consider it to contain a maximum of + * 64-bytes of data and 3-bytes of address. + */ + while (1) { + /* break condition */ + if (kthread_should_stop()) { + set_current_state(TASK_RUNNING); + break; + } + + /* + * If the message queue is empty, then there is no need to waste + * CPU cycles. So we let other processes execute, and continue + * from the beginning of the loop when we next get to run. + */ + spin_lock(&amd_spi->lock); + if (list_empty(&amd_spi->msg_queue)) { + spin_unlock(&amd_spi->lock); + schedule(); + continue; + } + + /* + * Else, pull the very first message from the queue and process + * all transfers within that message. And process the messages + * in a pure linear fashion. We also remove the spi_message + * from the queue. + */ + message = list_entry(amd_spi->msg_queue.next, + struct spi_message, queue); + list_del_init(&message->queue); + spin_unlock(&amd_spi->lock); + + /* We store the CS# line to be used for this spi_message */ + amd_spi->controller_data.chip_select = + message->spi->chip_select; + + /* Setting all variables to default value. */ + direction = i = 0; + opcode_found = 0; + recv_flag = tx_len = rx_len = 0; + cmd_opcode = 0; + buffer = NULL; + saved_index = 0; + + amd_spi_select_chip(master); + + /* + * This loop extracts spi_transfers from the spi message, + * programs the command into command register. Pointer variable + * *buffer* points to either tx_buf or rx_buf of spi_transfer + * depending on direction of transfer. Also programs FIFO of + * controller if data has to be transmitted. + */ + list_for_each_entry(transfer, &message->transfers, + transfer_list) + { + if(transfer->rx_buf != NULL) + direction = RECEIVE; + else if(transfer->tx_buf != NULL) + direction = TRANSMIT; + + switch (direction) { + case TRANSMIT: + buffer = (u8 *)transfer->tx_buf; + + if(opcode_found != 1) { + /* Store no. of bytes to be sent into + * FIFO */ + tx_len = transfer->len - 1; + /* Store opcode */ + cmd_opcode = *(u8 *)transfer->tx_buf; + /* Pointing to start of TX data */ + buffer++; + /* Program the command register*/ + amd_spi_set_opcode(master, cmd_opcode); + opcode_found = 1; + } else { + /* Store no. of bytes to be sent into + * FIFO */ + tx_len = transfer->len; + } + + /* Write data into the FIFO. */ + for (i = 0; i < tx_len; i++) { + iowrite8(buffer[i], + ((u8 *)amd_spi->io_remap_addr + + AMD_SPI_FIFO_BASE + + i + saved_index)); + } + + /* Set no. of bytes to be transmitted */ + amd_spi_set_tx_count(master, + tx_len + saved_index); + + /* + * Saving the index, from where next + * spi_transfer's data will be stored in FIFO. + */ + saved_index = i; + break; + case RECEIVE: + /* Store no. of bytes to be received from + * FIFO */ + rx_len = transfer->len; + buffer = (u8 *)transfer->rx_buf; + recv_flag=1; + break; + } + } + + /* Set the RX count to the number of bytes to expect in + * response */ + amd_spi_set_rx_count(master, rx_len ); + amd_spi_clear_fifo_ptr(master); + amd_spi_dump_reg(master); + /* Executing command */ + amd_spi_execute_opcode(master); + amd_spi_dump_reg(master); + + if(recv_flag == 1) { + /* Read data from FIFO to receive buffer */ + for (i = 0; i < rx_len; i++) { + buffer[i] = ioread8((u8 *)amd_spi->io_remap_addr + + AMD_SPI_FIFO_BASE + + tx_len + i); + } + + recv_flag = 0; + } + + /* Update statistics */ + message->actual_length = tx_len + rx_len + 1 ; + /* complete the transaction */ + message->status = 0; + message->complete(message->context); + } + + return 0; +} + +static int amd_spi_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct spi_master *master; + struct amd_spi *amd_spi; + u32 io_base_addr; + int err = 0; + + /* Allocate storage for spi_master and driver private data */ + master = spi_alloc_master(dev, sizeof(struct amd_spi)); + if (master == NULL) { + dev_err(dev, "Error allocating SPI master\n"); + return -ENOMEM; + } + + amd_spi = spi_master_get_devdata(master); + amd_spi->master = master; + + /* + * Lets first get the base address of SPI registers. The SPI Base + * Address is stored at offset 0xA0 into the LPC PCI configuration + * space. As per the specification, it is stored at bits 6:31 of the + * register. The address is aligned at 64-byte boundary, + * so we should just mask the lower 6 bits and get the address. + */ + pci_read_config_dword(pdev, AMD_PCI_LPC_SPI_BASE_ADDR_REG, + &io_base_addr); + amd_spi->io_base_addr = io_base_addr & AMD_SPI_BASE_ADDR_MASK; + amd_spi->io_remap_addr = ioremap_nocache(amd_spi->io_base_addr, + AMD_SPI_MEM_SIZE); + if (amd_spi->io_remap_addr == NULL) { + dev_err(dev, "ioremap of SPI registers failed\n"); + err = -ENOMEM; + goto err_free_master; + } + dev_dbg(dev, "io_base_addr: 0x%.8lx, io_remap_address: %p\n", + amd_spi->io_base_addr, amd_spi->io_remap_addr); + spin_lock_init(&amd_spi->lock); + INIT_LIST_HEAD(&amd_spi->msg_queue); + amd_spi->kthread_spi = kthread_run(amd_spi_thread, amd_spi, + "amd_spi_thread"); + + /* Now lets initialize the fields of spi_master */ + master->bus_num = 0; /* + * This should be the same as passed in + * spi_board_info structure + */ + master->num_chipselect = 4; /* Can be overwritten later during setup */ + master->mode_bits = 0; + master->flags = 0; + master->setup = amd_spi_master_setup; + master->transfer = amd_spi_master_transfer; + /* Register the controller with SPI framework */ + err = spi_register_master(master); + if (err) { + dev_err(dev, "error registering SPI controller\n"); + goto err_iounmap; + } + pci_set_drvdata(pdev, amd_spi); + + return 0; + +err_iounmap: + iounmap(amd_spi->io_remap_addr); +err_free_master: + spi_master_put(master); + + return 0; +} + +static void amd_spi_pci_remove(struct pci_dev *pdev) +{ + struct amd_spi *amd_spi = pci_get_drvdata(pdev); + + kthread_stop(amd_spi->kthread_spi); + iounmap(amd_spi->io_remap_addr); + spi_unregister_master(amd_spi->master); + spi_master_put(amd_spi->master); + pci_set_drvdata(pdev, NULL); +} + +static struct pci_driver amd_spi_pci_driver = { + .name = "amd_spi", + .id_table = amd_spi_pci_device_id, + .probe = amd_spi_pci_probe, + .remove = amd_spi_pci_remove, +}; + +static int __init amd_spi_init(void) +{ + int ret; + + pr_info("AMD SPI Driver v%s\n", SPI_VERSION); + + ret = pci_register_driver(&amd_spi_pci_driver); + if (ret) + return ret; + + return 0; +} +module_init(amd_spi_init); + +static void __exit amd_spi_exit(void) +{ + pci_unregister_driver(&amd_spi_pci_driver); +} +module_exit(amd_spi_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Arindam Nath <arindam.nath@amd.com>"); +MODULE_DESCRIPTION("AMD SPI Master Controller Driver"); |