| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Renesas RZ/V2H Renesas Serial Peripheral Interface (RSPI) |
| * |
| * Copyright (C) 2025 Renesas Electronics Corporation |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/bitops.h> |
| #include <linux/bits.h> |
| #include <linux/clk.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/limits.h> |
| #include <linux/log2.h> |
| #include <linux/math.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/property.h> |
| #include <linux/reset.h> |
| #include <linux/spi/spi.h> |
| #include <linux/wait.h> |
| |
| /* Registers */ |
| #define RSPI_SPDR 0x00 |
| #define RSPI_SPCR 0x08 |
| #define RSPI_SPPCR 0x0e |
| #define RSPI_SSLP 0x10 |
| #define RSPI_SPBR 0x11 |
| #define RSPI_SPSCR 0x13 |
| #define RSPI_SPCMD 0x14 |
| #define RSPI_SPDCR2 0x44 |
| #define RSPI_SPSR 0x52 |
| #define RSPI_SPSRC 0x6a |
| #define RSPI_SPFCR 0x6c |
| |
| /* Register SPCR */ |
| #define RSPI_SPCR_BPEN BIT(31) |
| #define RSPI_SPCR_MSTR BIT(30) |
| #define RSPI_SPCR_SPRIE BIT(17) |
| #define RSPI_SPCR_SCKASE BIT(12) |
| #define RSPI_SPCR_SPE BIT(0) |
| |
| /* Register SPPCR */ |
| #define RSPI_SPPCR_SPLP2 BIT(1) |
| |
| /* Register SPBR */ |
| #define RSPI_SPBR_SPR_MIN 0 |
| #define RSPI_SPBR_SPR_PCLK_MIN 1 |
| #define RSPI_SPBR_SPR_MAX 255 |
| |
| /* Register SPCMD */ |
| #define RSPI_SPCMD_SSLA GENMASK(25, 24) |
| #define RSPI_SPCMD_SPB GENMASK(20, 16) |
| #define RSPI_SPCMD_LSBF BIT(12) |
| #define RSPI_SPCMD_SSLKP BIT(7) |
| #define RSPI_SPCMD_BRDV GENMASK(3, 2) |
| #define RSPI_SPCMD_CPOL BIT(1) |
| #define RSPI_SPCMD_CPHA BIT(0) |
| |
| #define RSPI_SPCMD_BRDV_MIN 0 |
| #define RSPI_SPCMD_BRDV_MAX 3 |
| |
| /* Register SPDCR2 */ |
| #define RSPI_SPDCR2_TTRG GENMASK(11, 8) |
| #define RSPI_SPDCR2_RTRG GENMASK(3, 0) |
| |
| /* Register SPSR */ |
| #define RSPI_SPSR_SPRF BIT(15) |
| |
| /* Register RSPI_SPSRC */ |
| #define RSPI_SPSRC_CLEAR 0xfd80 |
| |
| #define RSPI_RESET_NUM 2 |
| |
| struct rzv2h_rspi_best_clock { |
| struct clk *clk; |
| unsigned long clk_rate; |
| unsigned long error; |
| u32 actual_hz; |
| u8 brdv; |
| u8 spr; |
| }; |
| |
| struct rzv2h_rspi_info { |
| void (*find_tclk_rate)(struct clk *clk, u32 hz, u8 spr_min, u8 spr_max, |
| struct rzv2h_rspi_best_clock *best_clk); |
| void (*find_pclk_rate)(struct clk *clk, u32 hz, u8 spr_low, u8 spr_high, |
| struct rzv2h_rspi_best_clock *best_clk); |
| const char *tclk_name; |
| unsigned int fifo_size; |
| unsigned int num_clks; |
| }; |
| |
| struct rzv2h_rspi_priv { |
| struct reset_control_bulk_data resets[RSPI_RESET_NUM]; |
| struct spi_controller *controller; |
| const struct rzv2h_rspi_info *info; |
| void __iomem *base; |
| struct clk *tclk; |
| struct clk *pclk; |
| wait_queue_head_t wait; |
| unsigned int bytes_per_word; |
| u32 last_speed_hz; |
| u32 freq; |
| u16 status; |
| u8 spr; |
| u8 brdv; |
| bool use_pclk; |
| }; |
| |
| #define RZV2H_RSPI_TX(func, type) \ |
| static inline void rzv2h_rspi_tx_##type(struct rzv2h_rspi_priv *rspi, \ |
| const void *txbuf, \ |
| unsigned int index) { \ |
| type buf = 0; \ |
| \ |
| if (txbuf) \ |
| buf = ((type *)txbuf)[index]; \ |
| \ |
| func(buf, rspi->base + RSPI_SPDR); \ |
| } |
| |
| #define RZV2H_RSPI_RX(func, type) \ |
| static inline void rzv2h_rspi_rx_##type(struct rzv2h_rspi_priv *rspi, \ |
| void *rxbuf, \ |
| unsigned int index) { \ |
| type buf = func(rspi->base + RSPI_SPDR); \ |
| \ |
| if (rxbuf) \ |
| ((type *)rxbuf)[index] = buf; \ |
| } |
| |
| RZV2H_RSPI_TX(writel, u32) |
| RZV2H_RSPI_TX(writew, u16) |
| RZV2H_RSPI_TX(writeb, u8) |
| RZV2H_RSPI_RX(readl, u32) |
| RZV2H_RSPI_RX(readw, u16) |
| RZV2H_RSPI_RX(readl, u8) |
| |
| static void rzv2h_rspi_reg_rmw(const struct rzv2h_rspi_priv *rspi, |
| int reg_offs, u32 bit_mask, u32 value) |
| { |
| u32 tmp; |
| |
| value <<= __ffs(bit_mask); |
| tmp = (readl(rspi->base + reg_offs) & ~bit_mask) | value; |
| writel(tmp, rspi->base + reg_offs); |
| } |
| |
| static inline void rzv2h_rspi_spe_disable(const struct rzv2h_rspi_priv *rspi) |
| { |
| rzv2h_rspi_reg_rmw(rspi, RSPI_SPCR, RSPI_SPCR_SPE, 0); |
| } |
| |
| static inline void rzv2h_rspi_spe_enable(const struct rzv2h_rspi_priv *rspi) |
| { |
| rzv2h_rspi_reg_rmw(rspi, RSPI_SPCR, RSPI_SPCR_SPE, 1); |
| } |
| |
| static inline void rzv2h_rspi_clear_fifos(const struct rzv2h_rspi_priv *rspi) |
| { |
| writeb(1, rspi->base + RSPI_SPFCR); |
| } |
| |
| static inline void rzv2h_rspi_clear_all_irqs(struct rzv2h_rspi_priv *rspi) |
| { |
| writew(RSPI_SPSRC_CLEAR, rspi->base + RSPI_SPSRC); |
| rspi->status = 0; |
| } |
| |
| static irqreturn_t rzv2h_rx_irq_handler(int irq, void *data) |
| { |
| struct rzv2h_rspi_priv *rspi = data; |
| |
| rspi->status = readw(rspi->base + RSPI_SPSR); |
| wake_up(&rspi->wait); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static inline int rzv2h_rspi_wait_for_interrupt(struct rzv2h_rspi_priv *rspi, |
| u32 wait_mask) |
| { |
| return wait_event_timeout(rspi->wait, (rspi->status & wait_mask), |
| HZ) == 0 ? -ETIMEDOUT : 0; |
| } |
| |
| static void rzv2h_rspi_send(struct rzv2h_rspi_priv *rspi, const void *txbuf, |
| unsigned int index) |
| { |
| switch (rspi->bytes_per_word) { |
| case 4: |
| rzv2h_rspi_tx_u32(rspi, txbuf, index); |
| break; |
| case 2: |
| rzv2h_rspi_tx_u16(rspi, txbuf, index); |
| break; |
| default: |
| rzv2h_rspi_tx_u8(rspi, txbuf, index); |
| } |
| } |
| |
| static int rzv2h_rspi_receive(struct rzv2h_rspi_priv *rspi, void *rxbuf, |
| unsigned int index) |
| { |
| int ret; |
| |
| ret = rzv2h_rspi_wait_for_interrupt(rspi, RSPI_SPSR_SPRF); |
| if (ret) |
| return ret; |
| |
| switch (rspi->bytes_per_word) { |
| case 4: |
| rzv2h_rspi_rx_u32(rspi, rxbuf, index); |
| break; |
| case 2: |
| rzv2h_rspi_rx_u16(rspi, rxbuf, index); |
| break; |
| default: |
| rzv2h_rspi_rx_u8(rspi, rxbuf, index); |
| } |
| |
| return 0; |
| } |
| |
| static int rzv2h_rspi_transfer_one(struct spi_controller *controller, |
| struct spi_device *spi, |
| struct spi_transfer *transfer) |
| { |
| struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(controller); |
| unsigned int words_to_transfer, i; |
| int ret = 0; |
| |
| transfer->effective_speed_hz = rspi->freq; |
| words_to_transfer = transfer->len / rspi->bytes_per_word; |
| |
| for (i = 0; i < words_to_transfer; i++) { |
| rzv2h_rspi_clear_all_irqs(rspi); |
| |
| rzv2h_rspi_send(rspi, transfer->tx_buf, i); |
| |
| ret = rzv2h_rspi_receive(rspi, transfer->rx_buf, i); |
| if (ret) |
| break; |
| } |
| |
| rzv2h_rspi_clear_all_irqs(rspi); |
| |
| if (ret) |
| transfer->error = SPI_TRANS_FAIL_IO; |
| |
| spi_finalize_current_transfer(controller); |
| |
| return ret; |
| } |
| |
| static inline u32 rzv2h_rspi_calc_bitrate(unsigned long tclk_rate, u8 spr, |
| u8 brdv) |
| { |
| return DIV_ROUND_UP(tclk_rate, (2 * (spr + 1) * (1 << brdv))); |
| } |
| |
| static void rzv2h_rspi_find_rate_variable(struct clk *clk, u32 hz, |
| u8 spr_min, u8 spr_max, |
| struct rzv2h_rspi_best_clock *best) |
| { |
| long clk_rate, clk_min_rate, clk_max_rate; |
| int min_rate_spr, max_rate_spr; |
| unsigned long error; |
| u32 actual_hz; |
| u8 brdv; |
| int spr; |
| |
| /* |
| * On T2H / N2H, the source for the SPI clock is PCLKSPIn, which is a |
| * 1/32, 1/30, 1/25 or 1/24 divider of PLL4, which is 2400MHz, |
| * resulting in either 75MHz, 80MHz, 96MHz or 100MHz. |
| */ |
| clk_min_rate = clk_round_rate(clk, 0); |
| if (clk_min_rate < 0) |
| return; |
| |
| clk_max_rate = clk_round_rate(clk, ULONG_MAX); |
| if (clk_max_rate < 0) |
| return; |
| |
| /* |
| * From the manual: |
| * Bit rate = f(PCLKSPIn) / (2 * (n + 1) * 2^N) |
| * |
| * If we adapt it to the current context, we get the following: |
| * hz = rate / ((spr + 1) * (1 << (brdv + 1))) |
| * |
| * This can be written in multiple forms depending on what we want to |
| * determine. |
| * |
| * To find the rate, having hz, spr and brdv: |
| * rate = hz * (spr + 1) * (1 << (brdv + 1) |
| * |
| * To find the spr, having rate, hz, and spr: |
| * spr = rate / (hz * (1 << (brdv + 1)) - 1 |
| */ |
| |
| for (brdv = RSPI_SPCMD_BRDV_MIN; brdv <= RSPI_SPCMD_BRDV_MAX; brdv++) { |
| /* Calculate the divisor needed to find the SPR from a rate. */ |
| u32 rate_div = hz * (1 << (brdv + 1)); |
| |
| /* |
| * If the SPR for the minimum rate is greater than the maximum |
| * allowed value skip this BRDV. The divisor increases with each |
| * BRDV iteration, so the following BRDV might result in a |
| * minimum SPR that is in the valid range. |
| */ |
| min_rate_spr = DIV_ROUND_CLOSEST(clk_min_rate, rate_div) - 1; |
| if (min_rate_spr > spr_max) |
| continue; |
| |
| /* |
| * If the SPR for the maximum rate is less than the minimum |
| * allowed value, exit. The divisor only increases with each |
| * BRDV iteration, so the following BRDV cannot result in a |
| * maximum SPR that is in the valid range. |
| */ |
| max_rate_spr = DIV_ROUND_CLOSEST(clk_max_rate, rate_div) - 1; |
| if (max_rate_spr < spr_min) |
| break; |
| |
| if (min_rate_spr < spr_min) |
| min_rate_spr = spr_min; |
| |
| if (max_rate_spr > spr_max) |
| max_rate_spr = spr_max; |
| |
| for (spr = min_rate_spr; spr <= max_rate_spr; spr++) { |
| clk_rate = (spr + 1) * rate_div; |
| |
| clk_rate = clk_round_rate(clk, clk_rate); |
| if (clk_rate <= 0) |
| continue; |
| |
| actual_hz = rzv2h_rspi_calc_bitrate(clk_rate, spr, brdv); |
| error = abs((long)hz - (long)actual_hz); |
| |
| if (error >= best->error) |
| continue; |
| |
| *best = (struct rzv2h_rspi_best_clock) { |
| .clk = clk, |
| .clk_rate = clk_rate, |
| .error = error, |
| .actual_hz = actual_hz, |
| .brdv = brdv, |
| .spr = spr, |
| }; |
| |
| if (!error) |
| return; |
| } |
| } |
| } |
| |
| static void rzv2h_rspi_find_rate_fixed(struct clk *clk, u32 hz, |
| u8 spr_min, u8 spr_max, |
| struct rzv2h_rspi_best_clock *best) |
| { |
| unsigned long clk_rate; |
| unsigned long error; |
| u32 actual_hz; |
| int spr; |
| u8 brdv; |
| |
| /* |
| * From the manual: |
| * Bit rate = f(RSPI_n_TCLK)/(2*(n+1)*2^(N)) |
| * |
| * Where: |
| * * RSPI_n_TCLK is fixed to 200MHz on V2H |
| * * n = SPR - is RSPI_SPBR.SPR (from 0 to 255) |
| * * N = BRDV - is RSPI_SPCMD.BRDV (from 0 to 3) |
| */ |
| clk_rate = clk_get_rate(clk); |
| for (brdv = RSPI_SPCMD_BRDV_MIN; brdv <= RSPI_SPCMD_BRDV_MAX; brdv++) { |
| spr = DIV_ROUND_UP(clk_rate, hz * (1 << (brdv + 1))); |
| spr--; |
| if (spr >= spr_min && spr <= spr_max) |
| goto clock_found; |
| } |
| |
| return; |
| |
| clock_found: |
| actual_hz = rzv2h_rspi_calc_bitrate(clk_rate, spr, brdv); |
| error = abs((long)hz - (long)actual_hz); |
| |
| if (error >= best->error) |
| return; |
| |
| *best = (struct rzv2h_rspi_best_clock) { |
| .clk = clk, |
| .clk_rate = clk_rate, |
| .error = error, |
| .actual_hz = actual_hz, |
| .brdv = brdv, |
| .spr = spr, |
| }; |
| } |
| |
| static u32 rzv2h_rspi_setup_clock(struct rzv2h_rspi_priv *rspi, u32 hz) |
| { |
| struct rzv2h_rspi_best_clock best_clock = { |
| .error = ULONG_MAX, |
| }; |
| int ret; |
| |
| rspi->info->find_tclk_rate(rspi->tclk, hz, RSPI_SPBR_SPR_MIN, |
| RSPI_SPBR_SPR_MAX, &best_clock); |
| |
| /* |
| * T2H and N2H can also use PCLK as a source, which is 125MHz, but not |
| * when both SPR and BRDV are 0. |
| */ |
| if (best_clock.error && rspi->info->find_pclk_rate) |
| rspi->info->find_pclk_rate(rspi->pclk, hz, RSPI_SPBR_SPR_PCLK_MIN, |
| RSPI_SPBR_SPR_MAX, &best_clock); |
| |
| if (!best_clock.clk_rate) |
| return -EINVAL; |
| |
| ret = clk_set_rate(best_clock.clk, best_clock.clk_rate); |
| if (ret) |
| return 0; |
| |
| rspi->use_pclk = best_clock.clk == rspi->pclk; |
| rspi->spr = best_clock.spr; |
| rspi->brdv = best_clock.brdv; |
| |
| return best_clock.actual_hz; |
| } |
| |
| static int rzv2h_rspi_prepare_message(struct spi_controller *ctlr, |
| struct spi_message *message) |
| { |
| struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(ctlr); |
| const struct spi_device *spi = message->spi; |
| struct spi_transfer *xfer; |
| u32 speed_hz = U32_MAX; |
| u8 bits_per_word; |
| u32 conf32; |
| u16 conf16; |
| u8 conf8; |
| |
| /* Make sure SPCR.SPE is 0 before amending the configuration */ |
| rzv2h_rspi_spe_disable(rspi); |
| |
| list_for_each_entry(xfer, &message->transfers, transfer_list) { |
| if (!xfer->speed_hz) |
| continue; |
| |
| speed_hz = min(xfer->speed_hz, speed_hz); |
| bits_per_word = xfer->bits_per_word; |
| } |
| |
| if (speed_hz == U32_MAX) |
| return -EINVAL; |
| |
| rspi->bytes_per_word = roundup_pow_of_two(BITS_TO_BYTES(bits_per_word)); |
| |
| if (speed_hz != rspi->last_speed_hz) { |
| rspi->freq = rzv2h_rspi_setup_clock(rspi, speed_hz); |
| if (!rspi->freq) |
| return -EINVAL; |
| |
| rspi->last_speed_hz = speed_hz; |
| } |
| |
| writeb(rspi->spr, rspi->base + RSPI_SPBR); |
| |
| /* Configure the device to work in "host" mode */ |
| conf32 = RSPI_SPCR_MSTR; |
| |
| /* Auto-stop function */ |
| conf32 |= RSPI_SPCR_SCKASE; |
| |
| /* SPI receive buffer full interrupt enable */ |
| conf32 |= RSPI_SPCR_SPRIE; |
| |
| /* Bypass synchronization circuit */ |
| conf32 |= FIELD_PREP(RSPI_SPCR_BPEN, rspi->use_pclk); |
| |
| writel(conf32, rspi->base + RSPI_SPCR); |
| |
| /* Use SPCMD0 only */ |
| writeb(0x0, rspi->base + RSPI_SPSCR); |
| |
| /* Setup loopback */ |
| conf8 = FIELD_PREP(RSPI_SPPCR_SPLP2, !!(spi->mode & SPI_LOOP)); |
| writeb(conf8, rspi->base + RSPI_SPPCR); |
| |
| /* Setup mode */ |
| conf32 = FIELD_PREP(RSPI_SPCMD_CPOL, !!(spi->mode & SPI_CPOL)); |
| conf32 |= FIELD_PREP(RSPI_SPCMD_CPHA, !!(spi->mode & SPI_CPHA)); |
| conf32 |= FIELD_PREP(RSPI_SPCMD_LSBF, !!(spi->mode & SPI_LSB_FIRST)); |
| conf32 |= FIELD_PREP(RSPI_SPCMD_SPB, bits_per_word - 1); |
| conf32 |= FIELD_PREP(RSPI_SPCMD_BRDV, rspi->brdv); |
| conf32 |= FIELD_PREP(RSPI_SPCMD_SSLKP, 1); |
| conf32 |= FIELD_PREP(RSPI_SPCMD_SSLA, spi_get_chipselect(spi, 0)); |
| writel(conf32, rspi->base + RSPI_SPCMD); |
| if (spi->mode & SPI_CS_HIGH) |
| writeb(BIT(spi_get_chipselect(spi, 0)), rspi->base + RSPI_SSLP); |
| else |
| writeb(0, rspi->base + RSPI_SSLP); |
| |
| /* Setup FIFO thresholds */ |
| conf16 = FIELD_PREP(RSPI_SPDCR2_TTRG, rspi->info->fifo_size - 1); |
| conf16 |= FIELD_PREP(RSPI_SPDCR2_RTRG, 0); |
| writew(conf16, rspi->base + RSPI_SPDCR2); |
| |
| rzv2h_rspi_clear_fifos(rspi); |
| |
| rzv2h_rspi_spe_enable(rspi); |
| |
| return 0; |
| } |
| |
| static int rzv2h_rspi_unprepare_message(struct spi_controller *ctlr, |
| struct spi_message *message) |
| { |
| struct rzv2h_rspi_priv *rspi = spi_controller_get_devdata(ctlr); |
| |
| rzv2h_rspi_spe_disable(rspi); |
| |
| return 0; |
| } |
| |
| static int rzv2h_rspi_probe(struct platform_device *pdev) |
| { |
| struct spi_controller *controller; |
| struct device *dev = &pdev->dev; |
| struct rzv2h_rspi_priv *rspi; |
| struct clk_bulk_data *clks; |
| int irq_rx, ret, i; |
| long tclk_rate; |
| |
| controller = devm_spi_alloc_host(dev, sizeof(*rspi)); |
| if (!controller) |
| return -ENOMEM; |
| |
| rspi = spi_controller_get_devdata(controller); |
| platform_set_drvdata(pdev, rspi); |
| |
| rspi->controller = controller; |
| |
| rspi->info = device_get_match_data(dev); |
| |
| rspi->base = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(rspi->base)) |
| return PTR_ERR(rspi->base); |
| |
| ret = devm_clk_bulk_get_all_enabled(dev, &clks); |
| if (ret != rspi->info->num_clks) |
| return dev_err_probe(dev, ret >= 0 ? -EINVAL : ret, |
| "cannot get clocks\n"); |
| for (i = 0; i < rspi->info->num_clks; i++) { |
| if (!strcmp(clks[i].id, rspi->info->tclk_name)) { |
| rspi->tclk = clks[i].clk; |
| } else if (rspi->info->find_pclk_rate && |
| !strcmp(clks[i].id, "pclk")) { |
| rspi->pclk = clks[i].clk; |
| } |
| } |
| |
| if (!rspi->tclk) |
| return dev_err_probe(dev, -EINVAL, "Failed to get tclk\n"); |
| |
| rspi->resets[0].id = "presetn"; |
| rspi->resets[1].id = "tresetn"; |
| ret = devm_reset_control_bulk_get_optional_exclusive(dev, RSPI_RESET_NUM, |
| rspi->resets); |
| if (ret) |
| return dev_err_probe(dev, ret, "cannot get resets\n"); |
| |
| irq_rx = platform_get_irq_byname(pdev, "rx"); |
| if (irq_rx < 0) |
| return dev_err_probe(dev, irq_rx, "cannot get IRQ 'rx'\n"); |
| |
| ret = reset_control_bulk_deassert(RSPI_RESET_NUM, rspi->resets); |
| if (ret) |
| return dev_err_probe(dev, ret, "failed to deassert resets\n"); |
| |
| init_waitqueue_head(&rspi->wait); |
| |
| ret = devm_request_irq(dev, irq_rx, rzv2h_rx_irq_handler, 0, |
| dev_name(dev), rspi); |
| if (ret) { |
| dev_err(dev, "cannot request `rx` IRQ\n"); |
| goto quit_resets; |
| } |
| |
| controller->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | |
| SPI_LSB_FIRST | SPI_LOOP; |
| controller->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); |
| controller->prepare_message = rzv2h_rspi_prepare_message; |
| controller->unprepare_message = rzv2h_rspi_unprepare_message; |
| controller->num_chipselect = 4; |
| controller->transfer_one = rzv2h_rspi_transfer_one; |
| |
| tclk_rate = clk_round_rate(rspi->tclk, 0); |
| if (tclk_rate < 0) { |
| ret = tclk_rate; |
| goto quit_resets; |
| } |
| |
| controller->min_speed_hz = rzv2h_rspi_calc_bitrate(tclk_rate, |
| RSPI_SPBR_SPR_MAX, |
| RSPI_SPCMD_BRDV_MAX); |
| |
| tclk_rate = clk_round_rate(rspi->tclk, ULONG_MAX); |
| if (tclk_rate < 0) { |
| ret = tclk_rate; |
| goto quit_resets; |
| } |
| |
| controller->max_speed_hz = rzv2h_rspi_calc_bitrate(tclk_rate, |
| RSPI_SPBR_SPR_MIN, |
| RSPI_SPCMD_BRDV_MIN); |
| |
| device_set_node(&controller->dev, dev_fwnode(dev)); |
| |
| ret = spi_register_controller(controller); |
| if (ret) { |
| dev_err(dev, "register controller failed\n"); |
| goto quit_resets; |
| } |
| |
| return 0; |
| |
| quit_resets: |
| reset_control_bulk_assert(RSPI_RESET_NUM, rspi->resets); |
| |
| return ret; |
| } |
| |
| static void rzv2h_rspi_remove(struct platform_device *pdev) |
| { |
| struct rzv2h_rspi_priv *rspi = platform_get_drvdata(pdev); |
| |
| spi_unregister_controller(rspi->controller); |
| |
| reset_control_bulk_assert(RSPI_RESET_NUM, rspi->resets); |
| } |
| |
| static const struct rzv2h_rspi_info rzv2h_info = { |
| .find_tclk_rate = rzv2h_rspi_find_rate_fixed, |
| .tclk_name = "tclk", |
| .fifo_size = 16, |
| .num_clks = 3, |
| }; |
| |
| static const struct rzv2h_rspi_info rzt2h_info = { |
| .find_tclk_rate = rzv2h_rspi_find_rate_variable, |
| .find_pclk_rate = rzv2h_rspi_find_rate_fixed, |
| .tclk_name = "pclkspi", |
| .fifo_size = 4, |
| .num_clks = 2, |
| }; |
| |
| static const struct of_device_id rzv2h_rspi_match[] = { |
| { .compatible = "renesas,r9a09g057-rspi", &rzv2h_info }, |
| { .compatible = "renesas,r9a09g077-rspi", &rzt2h_info }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, rzv2h_rspi_match); |
| |
| static struct platform_driver rzv2h_rspi_drv = { |
| .probe = rzv2h_rspi_probe, |
| .remove = rzv2h_rspi_remove, |
| .driver = { |
| .name = "rzv2h_rspi", |
| .of_match_table = rzv2h_rspi_match, |
| }, |
| }; |
| module_platform_driver(rzv2h_rspi_drv); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Fabrizio Castro <fabrizio.castro.jz@renesas.com>"); |
| MODULE_DESCRIPTION("Renesas RZ/V2H(P) Serial Peripheral Interface Driver"); |