mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
Compared to the previously supported RZ/V2H, the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs have a smaller FIFO, no resets, and only two clocks: PCLKSPIn and PCLK. PCLKSPIn, being the clock from which the SPI transfer clock is generated, is the equivalent of the TCLK clock from RZ/V2H. They also support generating the SPI transfer clock from PCLK. PCLKSPIn supports multiple dividers, generating multiple possible frequencies from its parent. To handle this, do the following changes. Use the minimum frequency of SPI clock to calculate the SPI controller's min_speed_hz, and the maximum frequency to calculate max_speed_hz. Add a new function, rzv2h_rspi_find_rate_variable(), which is used for the .find_tclk_rate() callback, and which supports handling clocks with a variable rate, with the following overall logic. Iterate through all possible BRDV values. For each BRDV, calculate two different SPRs, one for the clock's minimum frequency, and one for the maxmimum, and iterate through each SPR between them. If the minimum SPR is higher than the upper SPR limit, the minimum rate is too high to achieve the requested SPI frequency, skip to the next BRDV. For each SPR, calculate a rate and let the clock framework round it to the closest supported rate of the clock. The rate and SPR that generate a transfer frequency closest to the requested SPI transfer frequency will be picked. Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com> Link: https://patch.msgid.link/20251119161434.595677-12-cosmin-gabriel.tanislav.xa@renesas.com Signed-off-by: Mark Brown <broonie@kernel.org>
688 lines
17 KiB
C
688 lines
17 KiB
C
// 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");
|