18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// AMD SPI controller driver 48c2ecf20Sopenharmony_ci// 58c2ecf20Sopenharmony_ci// Copyright (c) 2020, Advanced Micro Devices, Inc. 68c2ecf20Sopenharmony_ci// 78c2ecf20Sopenharmony_ci// Author: Sanjay R Mehta <sanju.mehta@amd.com> 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/acpi.h> 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define AMD_SPI_CTRL0_REG 0x00 178c2ecf20Sopenharmony_ci#define AMD_SPI_EXEC_CMD BIT(16) 188c2ecf20Sopenharmony_ci#define AMD_SPI_FIFO_CLEAR BIT(20) 198c2ecf20Sopenharmony_ci#define AMD_SPI_BUSY BIT(31) 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define AMD_SPI_OPCODE_MASK 0xFF 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define AMD_SPI_ALT_CS_REG 0x1D 248c2ecf20Sopenharmony_ci#define AMD_SPI_ALT_CS_MASK 0x3 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define AMD_SPI_FIFO_BASE 0x80 278c2ecf20Sopenharmony_ci#define AMD_SPI_TX_COUNT_REG 0x48 288c2ecf20Sopenharmony_ci#define AMD_SPI_RX_COUNT_REG 0x4B 298c2ecf20Sopenharmony_ci#define AMD_SPI_STATUS_REG 0x4C 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define AMD_SPI_FIFO_SIZE 70 328c2ecf20Sopenharmony_ci#define AMD_SPI_MEM_SIZE 200 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci/* M_CMD OP codes for SPI */ 358c2ecf20Sopenharmony_ci#define AMD_SPI_XFER_TX 1 368c2ecf20Sopenharmony_ci#define AMD_SPI_XFER_RX 2 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistruct amd_spi { 398c2ecf20Sopenharmony_ci void __iomem *io_remap_addr; 408c2ecf20Sopenharmony_ci unsigned long io_base_addr; 418c2ecf20Sopenharmony_ci u32 rom_addr; 428c2ecf20Sopenharmony_ci u8 chip_select; 438c2ecf20Sopenharmony_ci}; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic inline u8 amd_spi_readreg8(struct spi_master *master, int idx) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci struct amd_spi *amd_spi = spi_master_get_devdata(master); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci return ioread8((u8 __iomem *)amd_spi->io_remap_addr + idx); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic inline void amd_spi_writereg8(struct spi_master *master, int idx, 538c2ecf20Sopenharmony_ci u8 val) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci struct amd_spi *amd_spi = spi_master_get_devdata(master); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci iowrite8(val, ((u8 __iomem *)amd_spi->io_remap_addr + idx)); 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic inline void amd_spi_setclear_reg8(struct spi_master *master, int idx, 618c2ecf20Sopenharmony_ci u8 set, u8 clear) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci u8 tmp = amd_spi_readreg8(master, idx); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci tmp = (tmp & ~clear) | set; 668c2ecf20Sopenharmony_ci amd_spi_writereg8(master, idx, tmp); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic inline u32 amd_spi_readreg32(struct spi_master *master, int idx) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci struct amd_spi *amd_spi = spi_master_get_devdata(master); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci return ioread32((u8 __iomem *)amd_spi->io_remap_addr + idx); 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic inline void amd_spi_writereg32(struct spi_master *master, int idx, 778c2ecf20Sopenharmony_ci u32 val) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct amd_spi *amd_spi = spi_master_get_devdata(master); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci iowrite32(val, ((u8 __iomem *)amd_spi->io_remap_addr + idx)); 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic inline void amd_spi_setclear_reg32(struct spi_master *master, int idx, 858c2ecf20Sopenharmony_ci u32 set, u32 clear) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci u32 tmp = amd_spi_readreg32(master, idx); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci tmp = (tmp & ~clear) | set; 908c2ecf20Sopenharmony_ci amd_spi_writereg32(master, idx, tmp); 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic void amd_spi_select_chip(struct spi_master *master) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci struct amd_spi *amd_spi = spi_master_get_devdata(master); 968c2ecf20Sopenharmony_ci u8 chip_select = amd_spi->chip_select; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci amd_spi_setclear_reg8(master, AMD_SPI_ALT_CS_REG, chip_select, 998c2ecf20Sopenharmony_ci AMD_SPI_ALT_CS_MASK); 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic void amd_spi_clear_fifo_ptr(struct spi_master *master) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci amd_spi_setclear_reg32(master, AMD_SPI_CTRL0_REG, AMD_SPI_FIFO_CLEAR, 1058c2ecf20Sopenharmony_ci AMD_SPI_FIFO_CLEAR); 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic void amd_spi_set_opcode(struct spi_master *master, u8 cmd_opcode) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci amd_spi_setclear_reg32(master, AMD_SPI_CTRL0_REG, cmd_opcode, 1118c2ecf20Sopenharmony_ci AMD_SPI_OPCODE_MASK); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic inline void amd_spi_set_rx_count(struct spi_master *master, 1158c2ecf20Sopenharmony_ci u8 rx_count) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci amd_spi_setclear_reg8(master, AMD_SPI_RX_COUNT_REG, rx_count, 0xff); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic inline void amd_spi_set_tx_count(struct spi_master *master, 1218c2ecf20Sopenharmony_ci u8 tx_count) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci amd_spi_setclear_reg8(master, AMD_SPI_TX_COUNT_REG, tx_count, 0xff); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic inline int amd_spi_busy_wait(struct amd_spi *amd_spi) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci bool spi_busy; 1298c2ecf20Sopenharmony_ci int timeout = 100000; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci /* poll for SPI bus to become idle */ 1328c2ecf20Sopenharmony_ci spi_busy = (ioread32((u8 __iomem *)amd_spi->io_remap_addr + 1338c2ecf20Sopenharmony_ci AMD_SPI_CTRL0_REG) & AMD_SPI_BUSY) == AMD_SPI_BUSY; 1348c2ecf20Sopenharmony_ci while (spi_busy) { 1358c2ecf20Sopenharmony_ci usleep_range(10, 20); 1368c2ecf20Sopenharmony_ci if (timeout-- < 0) 1378c2ecf20Sopenharmony_ci return -ETIMEDOUT; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci spi_busy = (ioread32((u8 __iomem *)amd_spi->io_remap_addr + 1408c2ecf20Sopenharmony_ci AMD_SPI_CTRL0_REG) & AMD_SPI_BUSY) == AMD_SPI_BUSY; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci return 0; 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic void amd_spi_execute_opcode(struct spi_master *master) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci struct amd_spi *amd_spi = spi_master_get_devdata(master); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* Set ExecuteOpCode bit in the CTRL0 register */ 1518c2ecf20Sopenharmony_ci amd_spi_setclear_reg32(master, AMD_SPI_CTRL0_REG, AMD_SPI_EXEC_CMD, 1528c2ecf20Sopenharmony_ci AMD_SPI_EXEC_CMD); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci amd_spi_busy_wait(amd_spi); 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic int amd_spi_master_setup(struct spi_device *spi) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci struct spi_master *master = spi->master; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci amd_spi_clear_fifo_ptr(master); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci return 0; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic inline int amd_spi_fifo_xfer(struct amd_spi *amd_spi, 1678c2ecf20Sopenharmony_ci struct spi_master *master, 1688c2ecf20Sopenharmony_ci struct spi_message *message) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci struct spi_transfer *xfer = NULL; 1718c2ecf20Sopenharmony_ci u8 cmd_opcode; 1728c2ecf20Sopenharmony_ci u8 *buf = NULL; 1738c2ecf20Sopenharmony_ci u32 m_cmd = 0; 1748c2ecf20Sopenharmony_ci u32 i = 0; 1758c2ecf20Sopenharmony_ci u32 tx_len = 0, rx_len = 0; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci list_for_each_entry(xfer, &message->transfers, 1788c2ecf20Sopenharmony_ci transfer_list) { 1798c2ecf20Sopenharmony_ci if (xfer->rx_buf) 1808c2ecf20Sopenharmony_ci m_cmd = AMD_SPI_XFER_RX; 1818c2ecf20Sopenharmony_ci if (xfer->tx_buf) 1828c2ecf20Sopenharmony_ci m_cmd = AMD_SPI_XFER_TX; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci if (m_cmd & AMD_SPI_XFER_TX) { 1858c2ecf20Sopenharmony_ci buf = (u8 *)xfer->tx_buf; 1868c2ecf20Sopenharmony_ci tx_len = xfer->len - 1; 1878c2ecf20Sopenharmony_ci cmd_opcode = *(u8 *)xfer->tx_buf; 1888c2ecf20Sopenharmony_ci buf++; 1898c2ecf20Sopenharmony_ci amd_spi_set_opcode(master, cmd_opcode); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci /* Write data into the FIFO. */ 1928c2ecf20Sopenharmony_ci for (i = 0; i < tx_len; i++) { 1938c2ecf20Sopenharmony_ci iowrite8(buf[i], 1948c2ecf20Sopenharmony_ci ((u8 __iomem *)amd_spi->io_remap_addr + 1958c2ecf20Sopenharmony_ci AMD_SPI_FIFO_BASE + i)); 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci amd_spi_set_tx_count(master, tx_len); 1998c2ecf20Sopenharmony_ci amd_spi_clear_fifo_ptr(master); 2008c2ecf20Sopenharmony_ci /* Execute command */ 2018c2ecf20Sopenharmony_ci amd_spi_execute_opcode(master); 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci if (m_cmd & AMD_SPI_XFER_RX) { 2048c2ecf20Sopenharmony_ci /* 2058c2ecf20Sopenharmony_ci * Store no. of bytes to be received from 2068c2ecf20Sopenharmony_ci * FIFO 2078c2ecf20Sopenharmony_ci */ 2088c2ecf20Sopenharmony_ci rx_len = xfer->len; 2098c2ecf20Sopenharmony_ci buf = (u8 *)xfer->rx_buf; 2108c2ecf20Sopenharmony_ci amd_spi_set_rx_count(master, rx_len); 2118c2ecf20Sopenharmony_ci amd_spi_clear_fifo_ptr(master); 2128c2ecf20Sopenharmony_ci /* Execute command */ 2138c2ecf20Sopenharmony_ci amd_spi_execute_opcode(master); 2148c2ecf20Sopenharmony_ci /* Read data from FIFO to receive buffer */ 2158c2ecf20Sopenharmony_ci for (i = 0; i < rx_len; i++) 2168c2ecf20Sopenharmony_ci buf[i] = amd_spi_readreg8(master, 2178c2ecf20Sopenharmony_ci AMD_SPI_FIFO_BASE + 2188c2ecf20Sopenharmony_ci tx_len + i); 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci /* Update statistics */ 2238c2ecf20Sopenharmony_ci message->actual_length = tx_len + rx_len + 1; 2248c2ecf20Sopenharmony_ci /* complete the transaction */ 2258c2ecf20Sopenharmony_ci message->status = 0; 2268c2ecf20Sopenharmony_ci spi_finalize_current_message(master); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci return 0; 2298c2ecf20Sopenharmony_ci} 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_cistatic int amd_spi_master_transfer(struct spi_master *master, 2328c2ecf20Sopenharmony_ci struct spi_message *msg) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci struct amd_spi *amd_spi = spi_master_get_devdata(master); 2358c2ecf20Sopenharmony_ci struct spi_device *spi = msg->spi; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci amd_spi->chip_select = spi->chip_select; 2388c2ecf20Sopenharmony_ci amd_spi_select_chip(master); 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci /* 2418c2ecf20Sopenharmony_ci * Extract spi_transfers from the spi message and 2428c2ecf20Sopenharmony_ci * program the controller. 2438c2ecf20Sopenharmony_ci */ 2448c2ecf20Sopenharmony_ci amd_spi_fifo_xfer(amd_spi, master, msg); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci return 0; 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cistatic size_t amd_spi_max_transfer_size(struct spi_device *spi) 2508c2ecf20Sopenharmony_ci{ 2518c2ecf20Sopenharmony_ci return AMD_SPI_FIFO_SIZE; 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int amd_spi_probe(struct platform_device *pdev) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2578c2ecf20Sopenharmony_ci struct spi_master *master; 2588c2ecf20Sopenharmony_ci struct amd_spi *amd_spi; 2598c2ecf20Sopenharmony_ci struct resource *res; 2608c2ecf20Sopenharmony_ci int err = 0; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci /* Allocate storage for spi_master and driver private data */ 2638c2ecf20Sopenharmony_ci master = spi_alloc_master(dev, sizeof(struct amd_spi)); 2648c2ecf20Sopenharmony_ci if (!master) { 2658c2ecf20Sopenharmony_ci dev_err(dev, "Error allocating SPI master\n"); 2668c2ecf20Sopenharmony_ci return -ENOMEM; 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci amd_spi = spi_master_get_devdata(master); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2728c2ecf20Sopenharmony_ci amd_spi->io_remap_addr = devm_ioremap_resource(&pdev->dev, res); 2738c2ecf20Sopenharmony_ci if (IS_ERR(amd_spi->io_remap_addr)) { 2748c2ecf20Sopenharmony_ci err = PTR_ERR(amd_spi->io_remap_addr); 2758c2ecf20Sopenharmony_ci dev_err(dev, "error %d ioremap of SPI registers failed\n", err); 2768c2ecf20Sopenharmony_ci goto err_free_master; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci dev_dbg(dev, "io_remap_address: %p\n", amd_spi->io_remap_addr); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci /* Initialize the spi_master fields */ 2818c2ecf20Sopenharmony_ci master->bus_num = 0; 2828c2ecf20Sopenharmony_ci master->num_chipselect = 4; 2838c2ecf20Sopenharmony_ci master->mode_bits = 0; 2848c2ecf20Sopenharmony_ci master->flags = SPI_MASTER_HALF_DUPLEX; 2858c2ecf20Sopenharmony_ci master->setup = amd_spi_master_setup; 2868c2ecf20Sopenharmony_ci master->transfer_one_message = amd_spi_master_transfer; 2878c2ecf20Sopenharmony_ci master->max_transfer_size = amd_spi_max_transfer_size; 2888c2ecf20Sopenharmony_ci master->max_message_size = amd_spi_max_transfer_size; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci /* Register the controller with SPI framework */ 2918c2ecf20Sopenharmony_ci err = devm_spi_register_master(dev, master); 2928c2ecf20Sopenharmony_ci if (err) { 2938c2ecf20Sopenharmony_ci dev_err(dev, "error %d registering SPI controller\n", err); 2948c2ecf20Sopenharmony_ci goto err_free_master; 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci return 0; 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_cierr_free_master: 3008c2ecf20Sopenharmony_ci spi_master_put(master); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci return err; 3038c2ecf20Sopenharmony_ci} 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI 3068c2ecf20Sopenharmony_cistatic const struct acpi_device_id spi_acpi_match[] = { 3078c2ecf20Sopenharmony_ci { "AMDI0061", 0 }, 3088c2ecf20Sopenharmony_ci {}, 3098c2ecf20Sopenharmony_ci}; 3108c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, spi_acpi_match); 3118c2ecf20Sopenharmony_ci#endif 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_cistatic struct platform_driver amd_spi_driver = { 3148c2ecf20Sopenharmony_ci .driver = { 3158c2ecf20Sopenharmony_ci .name = "amd_spi", 3168c2ecf20Sopenharmony_ci .acpi_match_table = ACPI_PTR(spi_acpi_match), 3178c2ecf20Sopenharmony_ci }, 3188c2ecf20Sopenharmony_ci .probe = amd_spi_probe, 3198c2ecf20Sopenharmony_ci}; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_cimodule_platform_driver(amd_spi_driver); 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 3248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sanjay Mehta <sanju.mehta@amd.com>"); 3258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("AMD SPI Master Controller Driver"); 326