| // SPDX-License-Identifier: GPL-2.0 | |
| // | |
| // Apple SoC SPI device driver | |
| // | |
| // Copyright The Asahi Linux Contributors | |
| // | |
| // Based on spi-sifive.c, Copyright 2018 SiFive, Inc. | |
| #include <linux/bitfield.h> | |
| #include <linux/bits.h> | |
| #include <linux/clk.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/io.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/pm_runtime.h> | |
| #include <linux/spi/spi.h> | |
| #define APPLE_SPI_CTRL 0x000 | |
| #define APPLE_SPI_CTRL_RUN BIT(0) | |
| #define APPLE_SPI_CTRL_TX_RESET BIT(2) | |
| #define APPLE_SPI_CTRL_RX_RESET BIT(3) | |
| #define APPLE_SPI_CFG 0x004 | |
| #define APPLE_SPI_CFG_CPHA BIT(1) | |
| #define APPLE_SPI_CFG_CPOL BIT(2) | |
| #define APPLE_SPI_CFG_MODE GENMASK(6, 5) | |
| #define APPLE_SPI_CFG_MODE_POLLED 0 | |
| #define APPLE_SPI_CFG_MODE_IRQ 1 | |
| #define APPLE_SPI_CFG_MODE_DMA 2 | |
| #define APPLE_SPI_CFG_IE_RXCOMPLETE BIT(7) | |
| #define APPLE_SPI_CFG_IE_TXRXTHRESH BIT(8) | |
| #define APPLE_SPI_CFG_LSB_FIRST BIT(13) | |
| #define APPLE_SPI_CFG_WORD_SIZE GENMASK(16, 15) | |
| #define APPLE_SPI_CFG_WORD_SIZE_8B 0 | |
| #define APPLE_SPI_CFG_WORD_SIZE_16B 1 | |
| #define APPLE_SPI_CFG_WORD_SIZE_32B 2 | |
| #define APPLE_SPI_CFG_FIFO_THRESH GENMASK(18, 17) | |
| #define APPLE_SPI_CFG_FIFO_THRESH_8B 0 | |
| #define APPLE_SPI_CFG_FIFO_THRESH_4B 1 | |
| #define APPLE_SPI_CFG_FIFO_THRESH_1B 2 | |
| #define APPLE_SPI_CFG_IE_TXCOMPLETE BIT(21) | |
| #define APPLE_SPI_STATUS 0x008 | |
| #define APPLE_SPI_STATUS_RXCOMPLETE BIT(0) | |
| #define APPLE_SPI_STATUS_TXRXTHRESH BIT(1) | |
| #define APPLE_SPI_STATUS_TXCOMPLETE BIT(2) | |
| #define APPLE_SPI_PIN 0x00c | |
| #define APPLE_SPI_PIN_KEEP_MOSI BIT(0) | |
| #define APPLE_SPI_PIN_CS BIT(1) | |
| #define APPLE_SPI_TXDATA 0x010 | |
| #define APPLE_SPI_RXDATA 0x020 | |
| #define APPLE_SPI_CLKDIV 0x030 | |
| #define APPLE_SPI_CLKDIV_MAX 0x7ff | |
| #define APPLE_SPI_RXCNT 0x034 | |
| #define APPLE_SPI_WORD_DELAY 0x038 | |
| #define APPLE_SPI_TXCNT 0x04c | |
| #define APPLE_SPI_FIFOSTAT 0x10c | |
| #define APPLE_SPI_FIFOSTAT_TXFULL BIT(4) | |
| #define APPLE_SPI_FIFOSTAT_LEVEL_TX GENMASK(15, 8) | |
| #define APPLE_SPI_FIFOSTAT_RXEMPTY BIT(20) | |
| #define APPLE_SPI_FIFOSTAT_LEVEL_RX GENMASK(31, 24) | |
| #define APPLE_SPI_IE_XFER 0x130 | |
| #define APPLE_SPI_IF_XFER 0x134 | |
| #define APPLE_SPI_XFER_RXCOMPLETE BIT(0) | |
| #define APPLE_SPI_XFER_TXCOMPLETE BIT(1) | |
| #define APPLE_SPI_IE_FIFO 0x138 | |
| #define APPLE_SPI_IF_FIFO 0x13c | |
| #define APPLE_SPI_FIFO_RXTHRESH BIT(4) | |
| #define APPLE_SPI_FIFO_TXTHRESH BIT(5) | |
| #define APPLE_SPI_FIFO_RXFULL BIT(8) | |
| #define APPLE_SPI_FIFO_TXEMPTY BIT(9) | |
| #define APPLE_SPI_FIFO_RXUNDERRUN BIT(16) | |
| #define APPLE_SPI_FIFO_TXOVERFLOW BIT(17) | |
| #define APPLE_SPI_SHIFTCFG 0x150 | |
| #define APPLE_SPI_SHIFTCFG_CLK_ENABLE BIT(0) | |
| #define APPLE_SPI_SHIFTCFG_CS_ENABLE BIT(1) | |
| #define APPLE_SPI_SHIFTCFG_AND_CLK_DATA BIT(8) | |
| #define APPLE_SPI_SHIFTCFG_CS_AS_DATA BIT(9) | |
| #define APPLE_SPI_SHIFTCFG_TX_ENABLE BIT(10) | |
| #define APPLE_SPI_SHIFTCFG_RX_ENABLE BIT(11) | |
| #define APPLE_SPI_SHIFTCFG_BITS GENMASK(21, 16) | |
| #define APPLE_SPI_SHIFTCFG_OVERRIDE_CS BIT(24) | |
| #define APPLE_SPI_PINCFG 0x154 | |
| #define APPLE_SPI_PINCFG_KEEP_CLK BIT(0) | |
| #define APPLE_SPI_PINCFG_KEEP_CS BIT(1) | |
| #define APPLE_SPI_PINCFG_KEEP_MOSI BIT(2) | |
| #define APPLE_SPI_PINCFG_CLK_IDLE_VAL BIT(8) | |
| #define APPLE_SPI_PINCFG_CS_IDLE_VAL BIT(9) | |
| #define APPLE_SPI_PINCFG_MOSI_IDLE_VAL BIT(10) | |
| #define APPLE_SPI_DELAY_PRE 0x160 | |
| #define APPLE_SPI_DELAY_POST 0x168 | |
| #define APPLE_SPI_DELAY_ENABLE BIT(0) | |
| #define APPLE_SPI_DELAY_NO_INTERBYTE BIT(1) | |
| #define APPLE_SPI_DELAY_SET_SCK BIT(4) | |
| #define APPLE_SPI_DELAY_SET_MOSI BIT(6) | |
| #define APPLE_SPI_DELAY_SCK_VAL BIT(8) | |
| #define APPLE_SPI_DELAY_MOSI_VAL BIT(12) | |
| #define APPLE_SPI_FIFO_DEPTH 16 | |
| /* | |
| * The slowest refclock available is 24MHz, the highest divider is 0x7ff, | |
| * the largest word size is 32 bits, the FIFO depth is 16, the maximum | |
| * intra-word delay is 0xffff refclocks. So the maximum time a transfer | |
| * cycle can take is: | |
| * | |
| * (0x7ff * 32 + 0xffff) * 16 / 24e6 Hz ~= 87ms | |
| * | |
| * Double it and round it up to 200ms for good measure. | |
| */ | |
| #define APPLE_SPI_TIMEOUT_MS 200 | |
| struct apple_spi { | |
| void __iomem *regs; /* MMIO register address */ | |
| struct clk *clk; /* bus clock */ | |
| struct completion done; /* wake-up from interrupt */ | |
| }; | |
| static inline void reg_write(struct apple_spi *spi, int offset, u32 value) | |
| { | |
| writel_relaxed(value, spi->regs + offset); | |
| } | |
| static inline u32 reg_read(struct apple_spi *spi, int offset) | |
| { | |
| return readl_relaxed(spi->regs + offset); | |
| } | |
| static inline void reg_mask(struct apple_spi *spi, int offset, u32 clear, u32 set) | |
| { | |
| u32 val = reg_read(spi, offset); | |
| val &= ~clear; | |
| val |= set; | |
| reg_write(spi, offset, val); | |
| } | |
| static void apple_spi_init(struct apple_spi *spi) | |
| { | |
| /* Set CS high (inactive) and disable override and auto-CS */ | |
| reg_write(spi, APPLE_SPI_PIN, APPLE_SPI_PIN_CS); | |
| reg_mask(spi, APPLE_SPI_SHIFTCFG, APPLE_SPI_SHIFTCFG_OVERRIDE_CS, 0); | |
| reg_mask(spi, APPLE_SPI_PINCFG, APPLE_SPI_PINCFG_CS_IDLE_VAL, APPLE_SPI_PINCFG_KEEP_CS); | |
| /* Reset FIFOs */ | |
| reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET); | |
| /* Configure defaults */ | |
| reg_write(spi, APPLE_SPI_CFG, | |
| FIELD_PREP(APPLE_SPI_CFG_FIFO_THRESH, APPLE_SPI_CFG_FIFO_THRESH_8B) | | |
| FIELD_PREP(APPLE_SPI_CFG_MODE, APPLE_SPI_CFG_MODE_IRQ) | | |
| FIELD_PREP(APPLE_SPI_CFG_WORD_SIZE, APPLE_SPI_CFG_WORD_SIZE_8B)); | |
| /* Disable IRQs */ | |
| reg_write(spi, APPLE_SPI_IE_FIFO, 0); | |
| reg_write(spi, APPLE_SPI_IE_XFER, 0); | |
| /* Disable delays */ | |
| reg_write(spi, APPLE_SPI_DELAY_PRE, 0); | |
| reg_write(spi, APPLE_SPI_DELAY_POST, 0); | |
| } | |
| static int apple_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *msg) | |
| { | |
| struct apple_spi *spi = spi_controller_get_devdata(ctlr); | |
| struct spi_device *device = msg->spi; | |
| u32 cfg = ((device->mode & SPI_CPHA ? APPLE_SPI_CFG_CPHA : 0) | | |
| (device->mode & SPI_CPOL ? APPLE_SPI_CFG_CPOL : 0) | | |
| (device->mode & SPI_LSB_FIRST ? APPLE_SPI_CFG_LSB_FIRST : 0)); | |
| /* Update core config */ | |
| reg_mask(spi, APPLE_SPI_CFG, | |
| APPLE_SPI_CFG_CPHA | APPLE_SPI_CFG_CPOL | APPLE_SPI_CFG_LSB_FIRST, cfg); | |
| return 0; | |
| } | |
| static void apple_spi_set_cs(struct spi_device *device, bool is_high) | |
| { | |
| struct apple_spi *spi = spi_controller_get_devdata(device->controller); | |
| reg_mask(spi, APPLE_SPI_PIN, APPLE_SPI_PIN_CS, is_high ? APPLE_SPI_PIN_CS : 0); | |
| } | |
| static bool apple_spi_prep_transfer(struct apple_spi *spi, struct spi_transfer *t) | |
| { | |
| u32 cr, fifo_threshold; | |
| /* Calculate and program the clock rate */ | |
| cr = DIV_ROUND_UP(clk_get_rate(spi->clk), t->speed_hz); | |
| reg_write(spi, APPLE_SPI_CLKDIV, min_t(u32, cr, APPLE_SPI_CLKDIV_MAX)); | |
| /* Update bits per word */ | |
| reg_mask(spi, APPLE_SPI_SHIFTCFG, APPLE_SPI_SHIFTCFG_BITS, | |
| FIELD_PREP(APPLE_SPI_SHIFTCFG_BITS, t->bits_per_word)); | |
| /* We will want to poll if the time we need to wait is | |
| * less than the context switching time. | |
| * Let's call that threshold 5us. The operation will take: | |
| * bits_per_word * fifo_threshold / hz <= 5 * 10^-6 | |
| * 200000 * bits_per_word * fifo_threshold <= hz | |
| */ | |
| fifo_threshold = APPLE_SPI_FIFO_DEPTH / 2; | |
| return (200000 * t->bits_per_word * fifo_threshold) <= t->speed_hz; | |
| } | |
| static irqreturn_t apple_spi_irq(int irq, void *dev_id) | |
| { | |
| struct apple_spi *spi = dev_id; | |
| u32 fifo = reg_read(spi, APPLE_SPI_IF_FIFO) & reg_read(spi, APPLE_SPI_IE_FIFO); | |
| u32 xfer = reg_read(spi, APPLE_SPI_IF_XFER) & reg_read(spi, APPLE_SPI_IE_XFER); | |
| if (fifo || xfer) { | |
| /* Disable interrupts until next transfer */ | |
| reg_write(spi, APPLE_SPI_IE_XFER, 0); | |
| reg_write(spi, APPLE_SPI_IE_FIFO, 0); | |
| complete(&spi->done); | |
| return IRQ_HANDLED; | |
| } | |
| return IRQ_NONE; | |
| } | |
| static int apple_spi_wait(struct apple_spi *spi, u32 fifo_bit, u32 xfer_bit, int poll) | |
| { | |
| int ret = 0; | |
| if (poll) { | |
| u32 fifo, xfer; | |
| unsigned long timeout = jiffies + APPLE_SPI_TIMEOUT_MS * HZ / 1000; | |
| do { | |
| fifo = reg_read(spi, APPLE_SPI_IF_FIFO); | |
| xfer = reg_read(spi, APPLE_SPI_IF_XFER); | |
| if (time_after(jiffies, timeout)) { | |
| ret = -ETIMEDOUT; | |
| break; | |
| } | |
| } while (!((fifo & fifo_bit) || (xfer & xfer_bit))); | |
| } else { | |
| reinit_completion(&spi->done); | |
| reg_write(spi, APPLE_SPI_IE_XFER, xfer_bit); | |
| reg_write(spi, APPLE_SPI_IE_FIFO, fifo_bit); | |
| if (!wait_for_completion_timeout(&spi->done, | |
| msecs_to_jiffies(APPLE_SPI_TIMEOUT_MS))) | |
| ret = -ETIMEDOUT; | |
| reg_write(spi, APPLE_SPI_IE_XFER, 0); | |
| reg_write(spi, APPLE_SPI_IE_FIFO, 0); | |
| } | |
| return ret; | |
| } | |
| static void apple_spi_tx(struct apple_spi *spi, const void **tx_ptr, u32 *left, | |
| unsigned int bytes_per_word) | |
| { | |
| u32 inuse, words, wrote; | |
| if (!*tx_ptr) | |
| return; | |
| inuse = FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_TX, reg_read(spi, APPLE_SPI_FIFOSTAT)); | |
| words = wrote = min_t(u32, *left, APPLE_SPI_FIFO_DEPTH - inuse); | |
| if (!words) | |
| return; | |
| *left -= words; | |
| switch (bytes_per_word) { | |
| case 1: { | |
| const u8 *p = *tx_ptr; | |
| while (words--) | |
| reg_write(spi, APPLE_SPI_TXDATA, *p++); | |
| break; | |
| } | |
| case 2: { | |
| const u16 *p = *tx_ptr; | |
| while (words--) | |
| reg_write(spi, APPLE_SPI_TXDATA, *p++); | |
| break; | |
| } | |
| case 4: { | |
| const u32 *p = *tx_ptr; | |
| while (words--) | |
| reg_write(spi, APPLE_SPI_TXDATA, *p++); | |
| break; | |
| } | |
| default: | |
| WARN_ON(1); | |
| } | |
| *tx_ptr = ((u8 *)*tx_ptr) + bytes_per_word * wrote; | |
| } | |
| static void apple_spi_rx(struct apple_spi *spi, void **rx_ptr, u32 *left, | |
| unsigned int bytes_per_word) | |
| { | |
| u32 words, read; | |
| if (!*rx_ptr) | |
| return; | |
| words = read = FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_RX, reg_read(spi, APPLE_SPI_FIFOSTAT)); | |
| WARN_ON(words > *left); | |
| if (!words) | |
| return; | |
| *left -= min_t(u32, *left, words); | |
| switch (bytes_per_word) { | |
| case 1: { | |
| u8 *p = *rx_ptr; | |
| while (words--) | |
| *p++ = reg_read(spi, APPLE_SPI_RXDATA); | |
| break; | |
| } | |
| case 2: { | |
| u16 *p = *rx_ptr; | |
| while (words--) | |
| *p++ = reg_read(spi, APPLE_SPI_RXDATA); | |
| break; | |
| } | |
| case 4: { | |
| u32 *p = *rx_ptr; | |
| while (words--) | |
| *p++ = reg_read(spi, APPLE_SPI_RXDATA); | |
| break; | |
| } | |
| default: | |
| WARN_ON(1); | |
| } | |
| *rx_ptr = ((u8 *)*rx_ptr) + bytes_per_word * read; | |
| } | |
| static int apple_spi_transfer_one(struct spi_controller *ctlr, struct spi_device *device, | |
| struct spi_transfer *t) | |
| { | |
| struct apple_spi *spi = spi_controller_get_devdata(ctlr); | |
| bool poll = apple_spi_prep_transfer(spi, t); | |
| const void *tx_ptr = t->tx_buf; | |
| void *rx_ptr = t->rx_buf; | |
| unsigned int bytes_per_word; | |
| u32 words, remaining_tx, remaining_rx; | |
| u32 xfer_flags = 0; | |
| u32 fifo_flags; | |
| int retries = 100; | |
| int ret = 0; | |
| if (t->bits_per_word > 16) | |
| bytes_per_word = 4; | |
| else if (t->bits_per_word > 8) | |
| bytes_per_word = 2; | |
| else | |
| bytes_per_word = 1; | |
| words = t->len / bytes_per_word; | |
| remaining_tx = tx_ptr ? words : 0; | |
| remaining_rx = rx_ptr ? words : 0; | |
| /* Reset FIFOs */ | |
| reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET); | |
| /* Clear IRQ flags */ | |
| reg_write(spi, APPLE_SPI_IF_XFER, ~0); | |
| reg_write(spi, APPLE_SPI_IF_FIFO, ~0); | |
| /* Determine transfer completion flags we wait for */ | |
| if (tx_ptr) | |
| xfer_flags |= APPLE_SPI_XFER_TXCOMPLETE; | |
| if (rx_ptr) | |
| xfer_flags |= APPLE_SPI_XFER_RXCOMPLETE; | |
| /* Set transfer length */ | |
| reg_write(spi, APPLE_SPI_TXCNT, remaining_tx); | |
| reg_write(spi, APPLE_SPI_RXCNT, remaining_rx); | |
| /* Prime transmit FIFO */ | |
| apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word); | |
| /* Start transfer */ | |
| reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RUN); | |
| /* TX again since a few words get popped off immediately */ | |
| apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word); | |
| while (xfer_flags) { | |
| fifo_flags = 0; | |
| if (remaining_tx) | |
| fifo_flags |= APPLE_SPI_FIFO_TXTHRESH; | |
| if (remaining_rx) | |
| fifo_flags |= APPLE_SPI_FIFO_RXTHRESH; | |
| /* Wait for anything to happen */ | |
| ret = apple_spi_wait(spi, fifo_flags, xfer_flags, poll); | |
| if (ret) { | |
| dev_err(&ctlr->dev, "transfer timed out (remaining %d tx, %d rx)\n", | |
| remaining_tx, remaining_rx); | |
| goto err; | |
| } | |
| /* Stop waiting on transfer halves once they complete */ | |
| xfer_flags &= ~reg_read(spi, APPLE_SPI_IF_XFER); | |
| /* Transmit and receive everything we can */ | |
| apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word); | |
| apple_spi_rx(spi, &rx_ptr, &remaining_rx, bytes_per_word); | |
| } | |
| /* | |
| * Sometimes the transfer completes before the last word is in the RX FIFO. | |
| * Normally one retry is all it takes to get the last word out. | |
| */ | |
| while (remaining_rx && retries--) | |
| apple_spi_rx(spi, &rx_ptr, &remaining_rx, bytes_per_word); | |
| if (remaining_tx) | |
| dev_err(&ctlr->dev, "transfer completed with %d words left to transmit\n", | |
| remaining_tx); | |
| if (remaining_rx) | |
| dev_err(&ctlr->dev, "transfer completed with %d words left to receive\n", | |
| remaining_rx); | |
| err: | |
| fifo_flags = reg_read(spi, APPLE_SPI_IF_FIFO); | |
| WARN_ON(fifo_flags & APPLE_SPI_FIFO_TXOVERFLOW); | |
| WARN_ON(fifo_flags & APPLE_SPI_FIFO_RXUNDERRUN); | |
| /* Stop transfer */ | |
| reg_write(spi, APPLE_SPI_CTRL, 0); | |
| return ret; | |
| } | |
| static int apple_spi_probe(struct platform_device *pdev) | |
| { | |
| struct apple_spi *spi; | |
| int ret, irq; | |
| struct spi_controller *ctlr; | |
| ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(struct apple_spi)); | |
| if (!ctlr) | |
| return -ENOMEM; | |
| spi = spi_controller_get_devdata(ctlr); | |
| init_completion(&spi->done); | |
| spi->regs = devm_platform_ioremap_resource(pdev, 0); | |
| if (IS_ERR(spi->regs)) | |
| return PTR_ERR(spi->regs); | |
| spi->clk = devm_clk_get_enabled(&pdev->dev, NULL); | |
| if (IS_ERR(spi->clk)) | |
| return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk), | |
| "Unable to find or enable bus clock\n"); | |
| irq = platform_get_irq(pdev, 0); | |
| if (irq < 0) | |
| return irq; | |
| ret = devm_request_irq(&pdev->dev, irq, apple_spi_irq, 0, | |
| dev_name(&pdev->dev), spi); | |
| if (ret) | |
| return dev_err_probe(&pdev->dev, ret, "Unable to bind to interrupt\n"); | |
| ctlr->bus_num = pdev->id; | |
| ctlr->num_chipselect = 1; | |
| ctlr->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST; | |
| ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); | |
| ctlr->prepare_message = apple_spi_prepare_message; | |
| ctlr->set_cs = apple_spi_set_cs; | |
| ctlr->transfer_one = apple_spi_transfer_one; | |
| ctlr->use_gpio_descriptors = true; | |
| ctlr->auto_runtime_pm = true; | |
| pm_runtime_set_active(&pdev->dev); | |
| ret = devm_pm_runtime_enable(&pdev->dev); | |
| if (ret < 0) | |
| return ret; | |
| apple_spi_init(spi); | |
| ret = devm_spi_register_controller(&pdev->dev, ctlr); | |
| if (ret < 0) | |
| return dev_err_probe(&pdev->dev, ret, "devm_spi_register_controller failed\n"); | |
| return 0; | |
| } | |
| static const struct of_device_id apple_spi_of_match[] = { | |
| { .compatible = "apple,t8103-spi", }, | |
| { .compatible = "apple,spi", }, | |
| {} | |
| }; | |
| MODULE_DEVICE_TABLE(of, apple_spi_of_match); | |
| static struct platform_driver apple_spi_driver = { | |
| .probe = apple_spi_probe, | |
| .driver = { | |
| .name = "apple-spi", | |
| .of_match_table = apple_spi_of_match, | |
| }, | |
| }; | |
| module_platform_driver(apple_spi_driver); | |
| MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); | |
| MODULE_DESCRIPTION("Apple SoC SPI driver"); | |
| MODULE_LICENSE("GPL"); |