| // SPDX-License-Identifier: GPL-2.0-or-later | |
| // | |
| // Nuvoton MA35D1 SPI controller driver | |
| // | |
| // Copyright (c) 2026 Nuvoton Technology Corp. | |
| // Author: Chi-Wen Weng <cwweng@nuvoton.com> | |
| #include <linux/bitfield.h> | |
| #include <linux/bits.h> | |
| #include <linux/clk.h> | |
| #include <linux/delay.h> | |
| #include <linux/device.h> | |
| #include <linux/io.h> | |
| #include <linux/iopoll.h> | |
| #include <linux/math.h> | |
| #include <linux/module.h> | |
| #include <linux/pm.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/property.h> | |
| #include <linux/reset.h> | |
| #include <linux/sizes.h> | |
| #include <linux/spi/spi.h> | |
| #include <linux/time.h> | |
| #include <linux/unaligned.h> | |
| /* Register offset definitions */ | |
| #define NUVOTON_SPI_CTL_OFFSET 0x00 | |
| #define NUVOTON_SPI_CLKDIV_OFFSET 0x04 | |
| #define NUVOTON_SPI_SSCTL_OFFSET 0x08 | |
| #define NUVOTON_SPI_PDMACTL_OFFSET 0x0c | |
| #define NUVOTON_SPI_FIFOCTL_OFFSET 0x10 | |
| #define NUVOTON_SPI_STATUS_OFFSET 0x14 | |
| #define NUVOTON_SPI_TX_OFFSET 0x20 | |
| #define NUVOTON_SPI_RX_OFFSET 0x30 | |
| /* SPI Control Register bit masks */ | |
| #define NUVOTON_SPI_CTL_DATDIR_MASK BIT(20) | |
| #define NUVOTON_SPI_CTL_REORDER_MASK BIT(19) | |
| #define NUVOTON_SPI_CTL_SLAVE_MASK BIT(18) | |
| #define NUVOTON_SPI_CTL_UNITIEN_MASK BIT(17) | |
| #define NUVOTON_SPI_CTL_RXONLY_MASK BIT(15) | |
| #define NUVOTON_SPI_CTL_HALFDPX_MASK BIT(14) | |
| #define NUVOTON_SPI_CTL_LSB_MASK BIT(13) | |
| #define NUVOTON_SPI_CTL_DWIDTH_MASK GENMASK(12, 8) | |
| #define NUVOTON_SPI_CTL_SUSPITV_MASK GENMASK(7, 4) | |
| #define NUVOTON_SPI_CTL_CLKPOL_MASK BIT(3) | |
| #define NUVOTON_SPI_CTL_TXNEG_MASK BIT(2) | |
| #define NUVOTON_SPI_CTL_RXNEG_MASK BIT(1) | |
| #define NUVOTON_SPI_CTL_SPIEN_MASK BIT(0) | |
| /* SPI Clock Divider Register bit masks */ | |
| #define NUVOTON_SPI_CLKDIV_MASK GENMASK(8, 0) | |
| /* SPI Slave Select Control Register bit masks */ | |
| #define NUVOTON_SPI_SSCTL_SS1_MASK BIT(1) | |
| #define NUVOTON_SPI_SSCTL_SS0_MASK BIT(0) | |
| #define NUVOTON_SPI_SSCTL_SSACTPOL_MASK BIT(2) | |
| #define NUVOTON_SPI_SSCTL_AUTOSS_MASK BIT(3) | |
| #define NUVOTON_SPI_SSCTL_SLV3WIRE_MASK BIT(4) | |
| #define NUVOTON_SPI_SSCTL_SLVBEIEN_MASK BIT(8) | |
| #define NUVOTON_SPI_SSCTL_SLVURIEN_MASK BIT(9) | |
| #define NUVOTON_SPI_SSCTL_SSACTIEN_MASK BIT(12) | |
| #define NUVOTON_SPI_SSCTL_SSINAIEN_MASK BIT(13) | |
| /* SPI PDMA Control Register bit masks */ | |
| #define NUVOTON_SPI_PDMACTL_TXPDMAEN_MASK BIT(0) | |
| #define NUVOTON_SPI_PDMACTL_RXPDMAEN_MASK BIT(1) | |
| /* SPI FIFO Control Register bit masks */ | |
| #define NUVOTON_SPI_FIFOCTL_SLVBERX_MASK BIT(10) | |
| #define NUVOTON_SPI_FIFOCTL_TXUFIEN_MASK BIT(7) | |
| #define NUVOTON_SPI_FIFOCTL_TXUFPOL_MASK BIT(6) | |
| #define NUVOTON_SPI_FIFOCTL_RXOVIEN_MASK BIT(5) | |
| #define NUVOTON_SPI_FIFOCTL_RXTOIEN_MASK BIT(4) | |
| #define NUVOTON_SPI_FIFOCTL_TXTHIEN_MASK BIT(3) | |
| #define NUVOTON_SPI_FIFOCTL_RXTHIEN_MASK BIT(2) | |
| #define NUVOTON_SPI_FIFOCTL_TXRST_MASK BIT(1) | |
| #define NUVOTON_SPI_FIFOCTL_RXRST_MASK BIT(0) | |
| /* SPI Status Register bit masks */ | |
| #define NUVOTON_SPI_STATUS_TXRXRST_MASK BIT(23) | |
| #define NUVOTON_SPI_STATUS_TXFULL_MASK BIT(17) | |
| #define NUVOTON_SPI_STATUS_SPIENSTS_MASK BIT(15) | |
| #define NUVOTON_SPI_STATUS_RXEMPTY_MASK BIT(8) | |
| #define NUVOTON_SPI_STATUS_BUSY_MASK BIT(0) | |
| #define NUVOTON_SPI_MAX_NATIVE_CS 2 | |
| #define NUVOTON_SPI_DEFAULT_NUM_CS 2 | |
| #define NUVOTON_SPI_DEFAULT_BPW 8 | |
| #define NUVOTON_SPI_MAX_SPEED_HZ 100000000U | |
| #define NUVOTON_SPI_MIN_DIVISOR 2U | |
| #define NUVOTON_SPI_MAX_DIVISOR 512U | |
| #define NUVOTON_SPI_RESET_CYCLES 5U | |
| /* Bound PIO operations to avoid long polling loops. */ | |
| #define NUVOTON_SPI_MAX_TRANSFER_SIZE SZ_4K | |
| #define NUVOTON_SPI_TIMEOUT_US 10000 | |
| struct nuvoton_spi { | |
| void __iomem *regs; | |
| struct device *dev; | |
| unsigned long clk_rate; | |
| }; | |
| static u32 nuvoton_spi_read(struct nuvoton_spi *nspi, u32 reg) | |
| { | |
| return readl(nspi->regs + reg); | |
| } | |
| static void nuvoton_spi_write(struct nuvoton_spi *nspi, u32 val, u32 reg) | |
| { | |
| writel(val, nspi->regs + reg); | |
| } | |
| static void nuvoton_spi_update_bits(struct nuvoton_spi *nspi, u32 reg, | |
| u32 mask, u32 val) | |
| { | |
| u32 tmp; | |
| tmp = nuvoton_spi_read(nspi, reg); | |
| tmp &= ~mask; | |
| tmp |= val & mask; | |
| nuvoton_spi_write(nspi, tmp, reg); | |
| } | |
| static int nuvoton_spi_disable(struct nuvoton_spi *nspi) | |
| { | |
| u32 ctl; | |
| u32 val; | |
| ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET); | |
| if (!(ctl & NUVOTON_SPI_CTL_SPIEN_MASK)) | |
| return 0; | |
| nuvoton_spi_write(nspi, ctl & ~NUVOTON_SPI_CTL_SPIEN_MASK, | |
| NUVOTON_SPI_CTL_OFFSET); | |
| return readl_poll_timeout_atomic(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, | |
| val, | |
| !(val & NUVOTON_SPI_STATUS_SPIENSTS_MASK), | |
| 1, NUVOTON_SPI_TIMEOUT_US); | |
| } | |
| static int nuvoton_spi_enable(struct nuvoton_spi *nspi) | |
| { | |
| u32 ctl; | |
| u32 val; | |
| ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET); | |
| if (ctl & NUVOTON_SPI_CTL_SPIEN_MASK) | |
| return 0; | |
| nuvoton_spi_write(nspi, ctl | NUVOTON_SPI_CTL_SPIEN_MASK, | |
| NUVOTON_SPI_CTL_OFFSET); | |
| return readl_poll_timeout_atomic(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, | |
| val, | |
| val & NUVOTON_SPI_STATUS_SPIENSTS_MASK, | |
| 1, NUVOTON_SPI_TIMEOUT_US); | |
| } | |
| static int nuvoton_spi_wait_ready(struct nuvoton_spi *nspi) | |
| { | |
| u32 val; | |
| return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val, | |
| !(val & NUVOTON_SPI_STATUS_BUSY_MASK), | |
| 0, NUVOTON_SPI_TIMEOUT_US); | |
| } | |
| static int nuvoton_spi_reset_fifo(struct nuvoton_spi *nspi) | |
| { | |
| u32 val; | |
| nuvoton_spi_update_bits(nspi, NUVOTON_SPI_FIFOCTL_OFFSET, | |
| NUVOTON_SPI_FIFOCTL_TXRST_MASK | | |
| NUVOTON_SPI_FIFOCTL_RXRST_MASK, | |
| NUVOTON_SPI_FIFOCTL_TXRST_MASK | | |
| NUVOTON_SPI_FIFOCTL_RXRST_MASK); | |
| /* | |
| * Give the controller a short time to latch the FIFO reset request | |
| * before polling the reset status bit. | |
| */ | |
| udelay(1); | |
| return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val, | |
| !(val & NUVOTON_SPI_STATUS_TXRXRST_MASK), | |
| 1, NUVOTON_SPI_TIMEOUT_US); | |
| } | |
| static int nuvoton_spi_wait_tx_not_full(struct nuvoton_spi *nspi) | |
| { | |
| u32 val; | |
| return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val, | |
| !(val & NUVOTON_SPI_STATUS_TXFULL_MASK), | |
| 0, NUVOTON_SPI_TIMEOUT_US); | |
| } | |
| static int nuvoton_spi_wait_rx_not_empty(struct nuvoton_spi *nspi) | |
| { | |
| u32 val; | |
| return readl_poll_timeout(nspi->regs + NUVOTON_SPI_STATUS_OFFSET, val, | |
| !(val & NUVOTON_SPI_STATUS_RXEMPTY_MASK), | |
| 0, NUVOTON_SPI_TIMEOUT_US); | |
| } | |
| static int nuvoton_spi_calc_divisor(unsigned long clk_rate, u32 speed_hz, | |
| unsigned int *divisor) | |
| { | |
| unsigned int div; | |
| div = DIV_ROUND_UP(clk_rate, speed_hz); | |
| if (div < NUVOTON_SPI_MIN_DIVISOR) | |
| div = NUVOTON_SPI_MIN_DIVISOR; | |
| /* | |
| * CLKDIV only accepts odd register values, corresponding to even | |
| * clock divisors. Round up so the generated clock never exceeds | |
| * the requested frequency. | |
| */ | |
| if (div & 1) | |
| div++; | |
| if (div > NUVOTON_SPI_MAX_DIVISOR) | |
| return -EINVAL; | |
| *divisor = div; | |
| return 0; | |
| } | |
| static int nuvoton_spi_configure_mode(struct nuvoton_spi *nspi, | |
| struct spi_device *spi) | |
| { | |
| u32 mask = NUVOTON_SPI_CTL_TXNEG_MASK | | |
| NUVOTON_SPI_CTL_RXNEG_MASK | | |
| NUVOTON_SPI_CTL_CLKPOL_MASK | | |
| NUVOTON_SPI_CTL_LSB_MASK; | |
| u32 ctl = 0; | |
| u32 cur_ctl; | |
| u32 mode; | |
| int ret; | |
| mode = spi->mode & SPI_MODE_X_MASK; | |
| if (mode == SPI_MODE_0 || mode == SPI_MODE_3) | |
| ctl |= NUVOTON_SPI_CTL_TXNEG_MASK; | |
| else | |
| ctl |= NUVOTON_SPI_CTL_RXNEG_MASK; | |
| if (spi->mode & SPI_CPOL) | |
| ctl |= NUVOTON_SPI_CTL_CLKPOL_MASK; | |
| if (spi->mode & SPI_LSB_FIRST) | |
| ctl |= NUVOTON_SPI_CTL_LSB_MASK; | |
| cur_ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET); | |
| if ((cur_ctl & mask) == ctl) | |
| return 0; | |
| ret = nuvoton_spi_disable(nspi); | |
| if (ret) | |
| return ret; | |
| nuvoton_spi_update_bits(nspi, NUVOTON_SPI_CTL_OFFSET, mask, ctl); | |
| return 0; | |
| } | |
| static int nuvoton_spi_configure_transfer(struct nuvoton_spi *nspi, | |
| struct spi_transfer *xfer) | |
| { | |
| u32 ctl_mask = NUVOTON_SPI_CTL_DWIDTH_MASK; | |
| u32 cur_clkdiv; | |
| u32 cur_ctl; | |
| u32 clkdiv; | |
| u32 dwidth; | |
| unsigned int divisor; | |
| int ret; | |
| ret = nuvoton_spi_calc_divisor(nspi->clk_rate, xfer->speed_hz, | |
| &divisor); | |
| if (ret) { | |
| dev_err(nspi->dev, "unsupported SPI clock %u Hz\n", | |
| xfer->speed_hz); | |
| return ret; | |
| } | |
| clkdiv = FIELD_PREP(NUVOTON_SPI_CLKDIV_MASK, divisor - 1); | |
| dwidth = xfer->bits_per_word == 32 ? 0 : xfer->bits_per_word; | |
| dwidth = FIELD_PREP(NUVOTON_SPI_CTL_DWIDTH_MASK, dwidth); | |
| xfer->effective_speed_hz = nspi->clk_rate / divisor; | |
| cur_ctl = nuvoton_spi_read(nspi, NUVOTON_SPI_CTL_OFFSET); | |
| cur_clkdiv = nuvoton_spi_read(nspi, NUVOTON_SPI_CLKDIV_OFFSET); | |
| if ((cur_ctl & ctl_mask) == dwidth && | |
| (cur_clkdiv & NUVOTON_SPI_CLKDIV_MASK) == clkdiv) | |
| return 0; | |
| ret = nuvoton_spi_disable(nspi); | |
| if (ret) | |
| return ret; | |
| if ((cur_ctl & ctl_mask) != dwidth) | |
| nuvoton_spi_update_bits(nspi, NUVOTON_SPI_CTL_OFFSET, | |
| ctl_mask, dwidth); | |
| if ((cur_clkdiv & NUVOTON_SPI_CLKDIV_MASK) != clkdiv) | |
| nuvoton_spi_write(nspi, clkdiv, NUVOTON_SPI_CLKDIV_OFFSET); | |
| return 0; | |
| } | |
| static u32 nuvoton_spi_get_tx_word(const void *txbuf, unsigned int offset, | |
| unsigned int bytes_per_word) | |
| { | |
| if (!txbuf) | |
| return 0; | |
| switch (bytes_per_word) { | |
| case 1: | |
| return ((const u8 *)txbuf)[offset]; | |
| case 2: | |
| return get_unaligned((const u16 *)((const u8 *)txbuf + offset)); | |
| case 4: | |
| return get_unaligned((const u32 *)((const u8 *)txbuf + offset)); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static void nuvoton_spi_put_rx_word(void *rxbuf, unsigned int offset, | |
| unsigned int bytes_per_word, u32 val) | |
| { | |
| if (!rxbuf) | |
| return; | |
| switch (bytes_per_word) { | |
| case 1: | |
| ((u8 *)rxbuf)[offset] = val; | |
| break; | |
| case 2: | |
| put_unaligned((u16)val, (u16 *)((u8 *)rxbuf + offset)); | |
| break; | |
| case 4: | |
| put_unaligned(val, (u32 *)((u8 *)rxbuf + offset)); | |
| break; | |
| } | |
| } | |
| static int nuvoton_spi_txrx(struct nuvoton_spi *nspi, | |
| struct spi_transfer *xfer) | |
| { | |
| unsigned int bytes_per_word; | |
| unsigned int offset; | |
| u32 val; | |
| int ret; | |
| bytes_per_word = spi_bpw_to_bytes(xfer->bits_per_word); | |
| /* | |
| * Use conservative word-by-word PIO. Each transmitted word produces | |
| * one receive FIFO entry, so always drain RX, including TX-only | |
| * transfers. RX-only transfers send zero-filled dummy words. | |
| */ | |
| for (offset = 0; offset < xfer->len; offset += bytes_per_word) { | |
| ret = nuvoton_spi_wait_tx_not_full(nspi); | |
| if (ret) { | |
| dev_err(nspi->dev, "TX FIFO full timeout\n"); | |
| return ret; | |
| } | |
| val = nuvoton_spi_get_tx_word(xfer->tx_buf, offset, | |
| bytes_per_word); | |
| nuvoton_spi_write(nspi, val, NUVOTON_SPI_TX_OFFSET); | |
| ret = nuvoton_spi_wait_rx_not_empty(nspi); | |
| if (ret) { | |
| dev_err(nspi->dev, "RX FIFO empty timeout\n"); | |
| return ret; | |
| } | |
| val = nuvoton_spi_read(nspi, NUVOTON_SPI_RX_OFFSET); | |
| nuvoton_spi_put_rx_word(xfer->rx_buf, offset, bytes_per_word, | |
| val); | |
| spi_delay_exec(&xfer->word_delay, xfer); | |
| } | |
| ret = nuvoton_spi_wait_ready(nspi); | |
| if (ret) | |
| dev_err(nspi->dev, "controller busy timeout\n"); | |
| return ret; | |
| } | |
| static void nuvoton_spi_set_cs_level(struct nuvoton_spi *nspi, | |
| unsigned int cs, bool assert) | |
| { | |
| u32 mask; | |
| switch (cs) { | |
| case 0: | |
| mask = NUVOTON_SPI_SSCTL_SS0_MASK; | |
| break; | |
| case 1: | |
| mask = NUVOTON_SPI_SSCTL_SS1_MASK; | |
| break; | |
| default: | |
| dev_warn(nspi->dev, "invalid chip select %u\n", cs); | |
| return; | |
| } | |
| nuvoton_spi_update_bits(nspi, NUVOTON_SPI_SSCTL_OFFSET, | |
| mask, assert ? mask : 0); | |
| } | |
| static int nuvoton_spi_setup(struct spi_device *spi) | |
| { | |
| unsigned int cs = spi_get_chipselect(spi, 0); | |
| if (spi_get_csgpiod(spi, 0)) | |
| return 0; | |
| if (cs >= NUVOTON_SPI_MAX_NATIVE_CS) { | |
| dev_err(&spi->dev, "invalid native chip select %u\n", cs); | |
| return -EINVAL; | |
| } | |
| if (spi->mode & SPI_CS_HIGH) { | |
| dev_err(&spi->dev, | |
| "active-high native chip select is not supported\n"); | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| static void nuvoton_spi_set_cs(struct spi_device *spi, bool level) | |
| { | |
| struct nuvoton_spi *nspi = spi_controller_get_devdata(spi->controller); | |
| int ret; | |
| /* | |
| * SSCTL may only be changed while SPIEN is clear. Clearing SPIEN | |
| * retains the CLK and MOSI output levels. | |
| */ | |
| ret = nuvoton_spi_disable(nspi); | |
| if (ret) { | |
| dev_err(nspi->dev, "failed to disable controller for chip select\n"); | |
| return; | |
| } | |
| /* | |
| * The SPI core passes the physical CS level to ->set_cs(). This | |
| * initial driver only supports active-low native chip selects. | |
| */ | |
| nuvoton_spi_set_cs_level(nspi, spi_get_chipselect(spi, 0), !level); | |
| } | |
| static int nuvoton_spi_prepare_message(struct spi_controller *ctlr, | |
| struct spi_message *message) | |
| { | |
| struct nuvoton_spi *nspi = spi_controller_get_devdata(ctlr); | |
| int ret; | |
| ret = nuvoton_spi_configure_mode(nspi, message->spi); | |
| if (ret) | |
| dev_err(nspi->dev, "failed to configure SPI mode\n"); | |
| return ret; | |
| } | |
| static int nuvoton_spi_transfer_one(struct spi_controller *ctlr, | |
| struct spi_device *spi, | |
| struct spi_transfer *xfer) | |
| { | |
| struct nuvoton_spi *nspi = spi_controller_get_devdata(ctlr); | |
| int ret; | |
| ret = nuvoton_spi_configure_transfer(nspi, xfer); | |
| if (ret) | |
| return ret; | |
| ret = nuvoton_spi_enable(nspi); | |
| if (ret) { | |
| dev_err(nspi->dev, "failed to enable controller\n"); | |
| return ret; | |
| } | |
| return nuvoton_spi_txrx(nspi, xfer); | |
| } | |
| static void nuvoton_spi_handle_err(struct spi_controller *ctlr, | |
| struct spi_message *message) | |
| { | |
| struct nuvoton_spi *nspi = spi_controller_get_devdata(ctlr); | |
| int ret; | |
| ret = nuvoton_spi_disable(nspi); | |
| if (ret) { | |
| dev_err(nspi->dev, | |
| "failed to disable controller during recovery\n"); | |
| return; | |
| } | |
| ret = nuvoton_spi_reset_fifo(nspi); | |
| if (ret) | |
| dev_err(nspi->dev, "failed to reset FIFO during recovery\n"); | |
| } | |
| static size_t nuvoton_spi_max_transfer_size(struct spi_device *spi) | |
| { | |
| return NUVOTON_SPI_MAX_TRANSFER_SIZE; | |
| } | |
| static int nuvoton_spi_hw_init(struct nuvoton_spi *nspi) | |
| { | |
| u32 ctl_mask; | |
| u32 fifo_mask; | |
| u32 ssctl_mask; | |
| int ret; | |
| ret = nuvoton_spi_disable(nspi); | |
| if (ret) { | |
| dev_err(nspi->dev, "failed to disable controller\n"); | |
| return ret; | |
| } | |
| ctl_mask = NUVOTON_SPI_CTL_DATDIR_MASK | | |
| NUVOTON_SPI_CTL_REORDER_MASK | | |
| NUVOTON_SPI_CTL_SLAVE_MASK | | |
| NUVOTON_SPI_CTL_UNITIEN_MASK | | |
| NUVOTON_SPI_CTL_RXONLY_MASK | | |
| NUVOTON_SPI_CTL_HALFDPX_MASK | | |
| NUVOTON_SPI_CTL_LSB_MASK | | |
| NUVOTON_SPI_CTL_DWIDTH_MASK | | |
| NUVOTON_SPI_CTL_SUSPITV_MASK | | |
| NUVOTON_SPI_CTL_CLKPOL_MASK | | |
| NUVOTON_SPI_CTL_TXNEG_MASK | | |
| NUVOTON_SPI_CTL_RXNEG_MASK; | |
| nuvoton_spi_update_bits(nspi, NUVOTON_SPI_CTL_OFFSET, ctl_mask, | |
| NUVOTON_SPI_CTL_TXNEG_MASK | | |
| FIELD_PREP(NUVOTON_SPI_CTL_DWIDTH_MASK, | |
| NUVOTON_SPI_DEFAULT_BPW)); | |
| ssctl_mask = NUVOTON_SPI_SSCTL_SS0_MASK | | |
| NUVOTON_SPI_SSCTL_SS1_MASK | | |
| NUVOTON_SPI_SSCTL_SSACTPOL_MASK | | |
| NUVOTON_SPI_SSCTL_AUTOSS_MASK | | |
| NUVOTON_SPI_SSCTL_SLV3WIRE_MASK | | |
| NUVOTON_SPI_SSCTL_SLVBEIEN_MASK | | |
| NUVOTON_SPI_SSCTL_SLVURIEN_MASK | | |
| NUVOTON_SPI_SSCTL_SSACTIEN_MASK | | |
| NUVOTON_SPI_SSCTL_SSINAIEN_MASK; | |
| nuvoton_spi_update_bits(nspi, NUVOTON_SPI_SSCTL_OFFSET, | |
| ssctl_mask, 0); | |
| nuvoton_spi_update_bits(nspi, NUVOTON_SPI_PDMACTL_OFFSET, | |
| NUVOTON_SPI_PDMACTL_TXPDMAEN_MASK | | |
| NUVOTON_SPI_PDMACTL_RXPDMAEN_MASK, 0); | |
| fifo_mask = NUVOTON_SPI_FIFOCTL_SLVBERX_MASK | | |
| NUVOTON_SPI_FIFOCTL_TXUFIEN_MASK | | |
| NUVOTON_SPI_FIFOCTL_TXUFPOL_MASK | | |
| NUVOTON_SPI_FIFOCTL_RXOVIEN_MASK | | |
| NUVOTON_SPI_FIFOCTL_RXTOIEN_MASK | | |
| NUVOTON_SPI_FIFOCTL_TXTHIEN_MASK | | |
| NUVOTON_SPI_FIFOCTL_RXTHIEN_MASK; | |
| nuvoton_spi_update_bits(nspi, NUVOTON_SPI_FIFOCTL_OFFSET, fifo_mask, 0); | |
| ret = nuvoton_spi_reset_fifo(nspi); | |
| if (ret) | |
| dev_err(nspi->dev, "FIFO reset timed out\n"); | |
| return ret; | |
| } | |
| static int nuvoton_spi_probe(struct platform_device *pdev) | |
| { | |
| struct device *dev = &pdev->dev; | |
| struct spi_controller *ctlr; | |
| struct nuvoton_spi *nspi; | |
| struct reset_control *rst; | |
| struct clk *clk; | |
| unsigned int max_divisor; | |
| u32 num_cs = NUVOTON_SPI_DEFAULT_NUM_CS; | |
| int ret; | |
| if (device_property_read_bool(dev, "spi-slave")) | |
| return dev_err_probe(dev, -EOPNOTSUPP, | |
| "target mode is not supported\n"); | |
| ctlr = devm_spi_alloc_host(dev, sizeof(*nspi)); | |
| if (!ctlr) | |
| return -ENOMEM; | |
| platform_set_drvdata(pdev, ctlr); | |
| nspi = spi_controller_get_devdata(ctlr); | |
| nspi->dev = dev; | |
| nspi->regs = devm_platform_ioremap_resource(pdev, 0); | |
| if (IS_ERR(nspi->regs)) | |
| return PTR_ERR(nspi->regs); | |
| clk = devm_clk_get_enabled(dev, NULL); | |
| if (IS_ERR(clk)) | |
| return dev_err_probe(dev, PTR_ERR(clk), | |
| "failed to get and enable clock\n"); | |
| nspi->clk_rate = clk_get_rate(clk); | |
| if (!nspi->clk_rate) | |
| return dev_err_probe(dev, -EINVAL, "invalid clock rate\n"); | |
| rst = devm_reset_control_get_exclusive_deasserted(dev, NULL); | |
| if (IS_ERR(rst)) | |
| return dev_err_probe(dev, PTR_ERR(rst), | |
| "failed to get and deassert reset\n"); | |
| /* | |
| * The hardware requires at least five peripheral clock cycles after | |
| * reset deassertion before programming controller registers. | |
| */ | |
| udelay(DIV_ROUND_UP_ULL((u64)NUVOTON_SPI_RESET_CYCLES * | |
| USEC_PER_SEC, nspi->clk_rate)); | |
| ret = device_property_read_u32(dev, "num-cs", &num_cs); | |
| if (ret && ret != -EINVAL) | |
| return dev_err_probe(dev, ret, "failed to read num-cs\n"); | |
| if (!num_cs || num_cs > NUVOTON_SPI_MAX_NATIVE_CS) | |
| return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", | |
| num_cs); | |
| ctlr->num_chipselect = num_cs; | |
| ctlr->max_native_cs = NUVOTON_SPI_MAX_NATIVE_CS; | |
| ctlr->use_gpio_descriptors = true; | |
| ctlr->max_transfer_size = nuvoton_spi_max_transfer_size; | |
| ctlr->setup = nuvoton_spi_setup; | |
| ctlr->set_cs = nuvoton_spi_set_cs; | |
| ctlr->prepare_message = nuvoton_spi_prepare_message; | |
| ctlr->transfer_one = nuvoton_spi_transfer_one; | |
| ctlr->handle_err = nuvoton_spi_handle_err; | |
| ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 32); | |
| ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST; | |
| ctlr->min_speed_hz = DIV_ROUND_UP(nspi->clk_rate, | |
| NUVOTON_SPI_MAX_DIVISOR); | |
| ret = nuvoton_spi_calc_divisor(nspi->clk_rate, | |
| NUVOTON_SPI_MAX_SPEED_HZ, &max_divisor); | |
| if (ret) | |
| return dev_err_probe(dev, ret, | |
| "clock rate does not support SPI transfers\n"); | |
| ctlr->max_speed_hz = nspi->clk_rate / max_divisor; | |
| ret = nuvoton_spi_hw_init(nspi); | |
| if (ret) | |
| return ret; | |
| ret = devm_spi_register_controller(dev, ctlr); | |
| if (ret) | |
| return dev_err_probe(dev, ret, | |
| "failed to register SPI controller\n"); | |
| return 0; | |
| } | |
| static int nuvoton_spi_suspend(struct device *dev) | |
| { | |
| struct spi_controller *ctlr = dev_get_drvdata(dev); | |
| return spi_controller_suspend(ctlr); | |
| } | |
| static int nuvoton_spi_resume(struct device *dev) | |
| { | |
| struct spi_controller *ctlr = dev_get_drvdata(dev); | |
| /* | |
| * The MA35D1 SPI registers are retained across power-down and deep | |
| * sleep, so the controller state does not need to be restored here. | |
| */ | |
| return spi_controller_resume(ctlr); | |
| } | |
| static DEFINE_SIMPLE_DEV_PM_OPS(nuvoton_spi_pm_ops, nuvoton_spi_suspend, | |
| nuvoton_spi_resume); | |
| static const struct of_device_id nuvoton_spi_of_match[] = { | |
| { .compatible = "nuvoton,ma35d1-spi" }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(of, nuvoton_spi_of_match); | |
| static struct platform_driver nuvoton_spi_driver = { | |
| .driver = { | |
| .name = "ma35d1-spi", | |
| .of_match_table = nuvoton_spi_of_match, | |
| .pm = pm_sleep_ptr(&nuvoton_spi_pm_ops), | |
| }, | |
| .probe = nuvoton_spi_probe, | |
| }; | |
| module_platform_driver(nuvoton_spi_driver); | |
| MODULE_DESCRIPTION("Nuvoton MA35D1 SPI controller driver"); | |
| MODULE_AUTHOR("Chi-Wen Weng <cwweng@nuvoton.com>"); | |
| MODULE_LICENSE("GPL"); |