Commit 73156083 authored by Mark Brown's avatar Mark Brown

Merge series "spi: meson-spicc: add support for AXG and G12A variants" from...

Merge series "spi: meson-spicc: add support for AXG and G12A variants" from Neil Armstrong <narmstrong@baylibre.com>:

The SPICC controller in Amlogic AXG & G12A is capable of driving the
CLK/MOSI/SS signal lines through the idle state which avoid the signals
floating in unexpected state, is capable of using linear clock divider
to reach a much fine tuned range of clocks, while the old controller only
uses a power of two clock divider, result at a more coarse clock range and
finally is capable of running at 80M clock.

The SPICC controller in Amlogic G12A takes the source clock from a specific
clock instead of the bus clock and has a different FIFO size and doesn't
handle the RX Half interrupt the same way as GXL & AXG variants. Thus
the burst management is simplified and takes in account a variable FIFO
size.

Now the controller can support frequencies higher than 30MHz, we need
the setup the I/O line delays in regard of the SPI clock frequency.

Neil Armstrong (7):
  spi: meson-spicc: remove unused variables
  spi: meson-spicc: support max 80MHz clock
  spi: meson-spicc: add min sclk for each compatible
  spi: meson-spicc: setup IO line delay
  spi: meson-spicc: adapt burst handling for G12A support
  dt-bindings: spi: amlogic,meson-gx-spicc: add Amlogic G12A compatible
  spi: meson-spicc: add support for Amlogic G12A

Sunny Luo (2):
  spi: meson-spicc: enhance output enable feature
  spi: meson-spicc: add a linear clock divider support

 .../bindings/spi/amlogic,meson-gx-spicc.yaml  |  22 +
 drivers/spi/Kconfig                           |   1 +
 drivers/spi/spi-meson-spicc.c                 | 496 +++++++++++++-----
 3 files changed, 392 insertions(+), 127 deletions(-)

--
2.22.0

_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
parents 5b16668e 9ea7db81
...@@ -22,6 +22,7 @@ properties: ...@@ -22,6 +22,7 @@ properties:
enum: enum:
- amlogic,meson-gx-spicc # SPICC controller on Amlogic GX and compatible SoCs - amlogic,meson-gx-spicc # SPICC controller on Amlogic GX and compatible SoCs
- amlogic,meson-axg-spicc # SPICC controller on Amlogic AXG and compatible SoCs - amlogic,meson-axg-spicc # SPICC controller on Amlogic AXG and compatible SoCs
- amlogic,meson-g12a-spicc # SPICC controller on Amlogic G12A and compatible SoCs
interrupts: interrupts:
maxItems: 1 maxItems: 1
...@@ -40,6 +41,27 @@ properties: ...@@ -40,6 +41,27 @@ properties:
items: items:
- const: core - const: core
if:
properties:
compatible:
contains:
enum:
- amlogic,meson-g12a-spicc
then:
properties:
clocks:
contains:
items:
- description: controller register bus clock
- description: baud rate generator and delay control clock
clock-names:
minItems: 2
items:
- const: core
- const: pclk
required: required:
- compatible - compatible
- reg - reg
......
...@@ -428,6 +428,7 @@ config SPI_FSL_ESPI ...@@ -428,6 +428,7 @@ config SPI_FSL_ESPI
config SPI_MESON_SPICC config SPI_MESON_SPICC
tristate "Amlogic Meson SPICC controller" tristate "Amlogic Meson SPICC controller"
depends on COMMON_CLK
depends on ARCH_MESON || COMPILE_TEST depends on ARCH_MESON || COMPILE_TEST
help help
This enables master mode support for the SPICC (SPI communication This enables master mode support for the SPICC (SPI communication
......
...@@ -9,11 +9,13 @@ ...@@ -9,11 +9,13 @@
#include <linux/bitfield.h> #include <linux/bitfield.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/spi/spi.h> #include <linux/spi/spi.h>
#include <linux/types.h> #include <linux/types.h>
...@@ -33,7 +35,6 @@ ...@@ -33,7 +35,6 @@
* to have a CS go down over the full transfer * to have a CS go down over the full transfer
*/ */
#define SPICC_MAX_FREQ 30000000
#define SPICC_MAX_BURST 128 #define SPICC_MAX_BURST 128
/* Register Map */ /* Register Map */
...@@ -105,7 +106,21 @@ ...@@ -105,7 +106,21 @@
#define SPICC_SWAP_RO BIT(14) /* RX FIFO Data Swap Read-Only */ #define SPICC_SWAP_RO BIT(14) /* RX FIFO Data Swap Read-Only */
#define SPICC_SWAP_W1 BIT(15) /* RX FIFO Data Swap Write-Only */ #define SPICC_SWAP_W1 BIT(15) /* RX FIFO Data Swap Write-Only */
#define SPICC_DLYCTL_RO_MASK GENMASK(20, 15) /* Delay Control Read-Only */ #define SPICC_DLYCTL_RO_MASK GENMASK(20, 15) /* Delay Control Read-Only */
#define SPICC_DLYCTL_W1_MASK GENMASK(21, 16) /* Delay Control Write-Only */ #define SPICC_MO_DELAY_MASK GENMASK(17, 16) /* Master Output Delay */
#define SPICC_MO_NO_DELAY 0
#define SPICC_MO_DELAY_1_CYCLE 1
#define SPICC_MO_DELAY_2_CYCLE 2
#define SPICC_MO_DELAY_3_CYCLE 3
#define SPICC_MI_DELAY_MASK GENMASK(19, 18) /* Master Input Delay */
#define SPICC_MI_NO_DELAY 0
#define SPICC_MI_DELAY_1_CYCLE 1
#define SPICC_MI_DELAY_2_CYCLE 2
#define SPICC_MI_DELAY_3_CYCLE 3
#define SPICC_MI_CAP_DELAY_MASK GENMASK(21, 20) /* Master Capture Delay */
#define SPICC_CAP_AHEAD_2_CYCLE 0
#define SPICC_CAP_AHEAD_1_CYCLE 1
#define SPICC_CAP_NO_DELAY 2
#define SPICC_CAP_DELAY_1_CYCLE 3
#define SPICC_FIFORST_RO_MASK GENMASK(22, 21) /* FIFO Softreset Read-Only */ #define SPICC_FIFORST_RO_MASK GENMASK(22, 21) /* FIFO Softreset Read-Only */
#define SPICC_FIFORST_W1_MASK GENMASK(23, 22) /* FIFO Softreset Write-Only */ #define SPICC_FIFORST_W1_MASK GENMASK(23, 22) /* FIFO Softreset Write-Only */
...@@ -113,31 +128,59 @@ ...@@ -113,31 +128,59 @@
#define SPICC_DWADDR 0x24 /* Write Address of DMA */ #define SPICC_DWADDR 0x24 /* Write Address of DMA */
#define SPICC_ENH_CTL0 0x38 /* Enhanced Feature */
#define SPICC_ENH_CLK_CS_DELAY_MASK GENMASK(15, 0)
#define SPICC_ENH_DATARATE_MASK GENMASK(23, 16)
#define SPICC_ENH_DATARATE_EN BIT(24)
#define SPICC_ENH_MOSI_OEN BIT(25)
#define SPICC_ENH_CLK_OEN BIT(26)
#define SPICC_ENH_CS_OEN BIT(27)
#define SPICC_ENH_CLK_CS_DELAY_EN BIT(28)
#define SPICC_ENH_MAIN_CLK_AO BIT(29)
#define writel_bits_relaxed(mask, val, addr) \ #define writel_bits_relaxed(mask, val, addr) \
writel_relaxed((readl_relaxed(addr) & ~(mask)) | (val), addr) writel_relaxed((readl_relaxed(addr) & ~(mask)) | (val), addr)
#define SPICC_BURST_MAX 16 struct meson_spicc_data {
#define SPICC_FIFO_HALF 10 unsigned int max_speed_hz;
unsigned int min_speed_hz;
unsigned int fifo_size;
bool has_oen;
bool has_enhance_clk_div;
bool has_pclk;
};
struct meson_spicc_device { struct meson_spicc_device {
struct spi_master *master; struct spi_master *master;
struct platform_device *pdev; struct platform_device *pdev;
void __iomem *base; void __iomem *base;
struct clk *core; struct clk *core;
struct clk *pclk;
struct clk *clk;
struct spi_message *message; struct spi_message *message;
struct spi_transfer *xfer; struct spi_transfer *xfer;
const struct meson_spicc_data *data;
u8 *tx_buf; u8 *tx_buf;
u8 *rx_buf; u8 *rx_buf;
unsigned int bytes_per_word; unsigned int bytes_per_word;
unsigned long tx_remain; unsigned long tx_remain;
unsigned long txb_remain;
unsigned long rx_remain; unsigned long rx_remain;
unsigned long rxb_remain;
unsigned long xfer_remain; unsigned long xfer_remain;
bool is_burst_end;
bool is_last_burst;
}; };
static void meson_spicc_oen_enable(struct meson_spicc_device *spicc)
{
u32 conf;
if (!spicc->data->has_oen)
return;
conf = readl_relaxed(spicc->base + SPICC_ENH_CTL0) |
SPICC_ENH_MOSI_OEN | SPICC_ENH_CLK_OEN | SPICC_ENH_CS_OEN;
writel_relaxed(conf, spicc->base + SPICC_ENH_CTL0);
}
static inline bool meson_spicc_txfull(struct meson_spicc_device *spicc) static inline bool meson_spicc_txfull(struct meson_spicc_device *spicc)
{ {
return !!FIELD_GET(SPICC_TF, return !!FIELD_GET(SPICC_TF,
...@@ -146,7 +189,7 @@ static inline bool meson_spicc_txfull(struct meson_spicc_device *spicc) ...@@ -146,7 +189,7 @@ static inline bool meson_spicc_txfull(struct meson_spicc_device *spicc)
static inline bool meson_spicc_rxready(struct meson_spicc_device *spicc) static inline bool meson_spicc_rxready(struct meson_spicc_device *spicc)
{ {
return FIELD_GET(SPICC_RH | SPICC_RR | SPICC_RF_EN, return FIELD_GET(SPICC_RH | SPICC_RR | SPICC_RF,
readl_relaxed(spicc->base + SPICC_STATREG)); readl_relaxed(spicc->base + SPICC_STATREG));
} }
...@@ -201,34 +244,22 @@ static inline void meson_spicc_tx(struct meson_spicc_device *spicc) ...@@ -201,34 +244,22 @@ static inline void meson_spicc_tx(struct meson_spicc_device *spicc)
spicc->base + SPICC_TXDATA); spicc->base + SPICC_TXDATA);
} }
static inline u32 meson_spicc_setup_rx_irq(struct meson_spicc_device *spicc, static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc)
u32 irq_ctrl)
{ {
if (spicc->rx_remain > SPICC_FIFO_HALF)
irq_ctrl |= SPICC_RH_EN;
else
irq_ctrl |= SPICC_RR_EN;
return irq_ctrl;
}
static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc, unsigned int burst_len = min_t(unsigned int,
unsigned int burst_len) spicc->xfer_remain /
{ spicc->bytes_per_word,
spicc->data->fifo_size);
/* Setup Xfer variables */ /* Setup Xfer variables */
spicc->tx_remain = burst_len; spicc->tx_remain = burst_len;
spicc->rx_remain = burst_len; spicc->rx_remain = burst_len;
spicc->xfer_remain -= burst_len * spicc->bytes_per_word; spicc->xfer_remain -= burst_len * spicc->bytes_per_word;
spicc->is_burst_end = false;
if (burst_len < SPICC_BURST_MAX || !spicc->xfer_remain)
spicc->is_last_burst = true;
else
spicc->is_last_burst = false;
/* Setup burst length */ /* Setup burst length */
writel_bits_relaxed(SPICC_BURSTLENGTH_MASK, writel_bits_relaxed(SPICC_BURSTLENGTH_MASK,
FIELD_PREP(SPICC_BURSTLENGTH_MASK, FIELD_PREP(SPICC_BURSTLENGTH_MASK,
burst_len), burst_len - 1),
spicc->base + SPICC_CONREG); spicc->base + SPICC_CONREG);
/* Fill TX FIFO */ /* Fill TX FIFO */
...@@ -238,97 +269,71 @@ static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc, ...@@ -238,97 +269,71 @@ static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc,
static irqreturn_t meson_spicc_irq(int irq, void *data) static irqreturn_t meson_spicc_irq(int irq, void *data)
{ {
struct meson_spicc_device *spicc = (void *) data; struct meson_spicc_device *spicc = (void *) data;
u32 ctrl = readl_relaxed(spicc->base + SPICC_INTREG);
u32 stat = readl_relaxed(spicc->base + SPICC_STATREG) & ctrl;
ctrl &= ~(SPICC_RH_EN | SPICC_RR_EN); writel_bits_relaxed(SPICC_TC, SPICC_TC, spicc->base + SPICC_STATREG);
/* Empty RX FIFO */ /* Empty RX FIFO */
meson_spicc_rx(spicc); meson_spicc_rx(spicc);
/* Enable TC interrupt since we transferred everything */ if (!spicc->xfer_remain) {
if (!spicc->tx_remain && !spicc->rx_remain) { /* Disable all IRQs */
spicc->is_burst_end = true; writel(0, spicc->base + SPICC_INTREG);
/* Enable TC interrupt */
ctrl |= SPICC_TC_EN;
/* Reload IRQ status */ spi_finalize_current_transfer(spicc->master);
stat = readl_relaxed(spicc->base + SPICC_STATREG) & ctrl;
}
/* Check transfer complete */
if ((stat & SPICC_TC) && spicc->is_burst_end) {
unsigned int burst_len;
/* Clear TC bit */
writel_relaxed(SPICC_TC, spicc->base + SPICC_STATREG);
/* Disable TC interrupt */
ctrl &= ~SPICC_TC_EN;
if (spicc->is_last_burst) {
/* Disable all IRQs */
writel(0, spicc->base + SPICC_INTREG);
spi_finalize_current_transfer(spicc->master);
return IRQ_HANDLED;
}
burst_len = min_t(unsigned int,
spicc->xfer_remain / spicc->bytes_per_word,
SPICC_BURST_MAX);
/* Setup burst */ return IRQ_HANDLED;
meson_spicc_setup_burst(spicc, burst_len);
/* Restart burst */
writel_bits_relaxed(SPICC_XCH, SPICC_XCH,
spicc->base + SPICC_CONREG);
} }
/* Setup RX interrupt trigger */ /* Setup burst */
ctrl = meson_spicc_setup_rx_irq(spicc, ctrl); meson_spicc_setup_burst(spicc);
/* Reconfigure interrupts */ /* Start burst */
writel(ctrl, spicc->base + SPICC_INTREG); writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
static u32 meson_spicc_setup_speed(struct meson_spicc_device *spicc, u32 conf, static void meson_spicc_auto_io_delay(struct meson_spicc_device *spicc)
u32 speed)
{ {
unsigned long parent, value; u32 div, hz;
unsigned int i, div; u32 mi_delay, cap_delay;
u32 conf;
parent = clk_get_rate(spicc->core);
if (spicc->data->has_enhance_clk_div) {
/* Find closest inferior/equal possible speed */ div = FIELD_GET(SPICC_ENH_DATARATE_MASK,
for (i = 0 ; i < 7 ; ++i) { readl_relaxed(spicc->base + SPICC_ENH_CTL0));
/* 2^(data_rate+2) */ div++;
value = parent >> (i + 2); div <<= 1;
} else {
if (value <= speed) div = FIELD_GET(SPICC_DATARATE_MASK,
break; readl_relaxed(spicc->base + SPICC_CONREG));
div += 2;
div = 1 << div;
} }
/* If provided speed it lower than max divider, use max divider */ mi_delay = SPICC_MI_NO_DELAY;
if (i > 7) { cap_delay = SPICC_CAP_AHEAD_2_CYCLE;
div = 7; hz = clk_get_rate(spicc->clk);
dev_warn_once(&spicc->pdev->dev, "unable to get close to speed %u\n",
speed); if (hz >= 100000000)
} else cap_delay = SPICC_CAP_DELAY_1_CYCLE;
div = i; else if (hz >= 80000000)
cap_delay = SPICC_CAP_NO_DELAY;
dev_dbg(&spicc->pdev->dev, "parent %lu, speed %u -> %lu (%u)\n", else if (hz >= 40000000)
parent, speed, value, div); cap_delay = SPICC_CAP_AHEAD_1_CYCLE;
else if (div >= 16)
conf &= ~SPICC_DATARATE_MASK; mi_delay = SPICC_MI_DELAY_3_CYCLE;
conf |= FIELD_PREP(SPICC_DATARATE_MASK, div); else if (div >= 8)
mi_delay = SPICC_MI_DELAY_2_CYCLE;
return conf; else if (div >= 6)
mi_delay = SPICC_MI_DELAY_1_CYCLE;
conf = readl_relaxed(spicc->base + SPICC_TESTREG);
conf &= ~(SPICC_MO_DELAY_MASK | SPICC_MI_DELAY_MASK
| SPICC_MI_CAP_DELAY_MASK);
conf |= FIELD_PREP(SPICC_MI_DELAY_MASK, mi_delay);
conf |= FIELD_PREP(SPICC_MI_CAP_DELAY_MASK, cap_delay);
writel_relaxed(conf, spicc->base + SPICC_TESTREG);
} }
static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc, static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc,
...@@ -339,9 +344,6 @@ static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc, ...@@ -339,9 +344,6 @@ static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc,
/* Read original configuration */ /* Read original configuration */
conf = conf_orig = readl_relaxed(spicc->base + SPICC_CONREG); conf = conf_orig = readl_relaxed(spicc->base + SPICC_CONREG);
/* Select closest divider */
conf = meson_spicc_setup_speed(spicc, conf, xfer->speed_hz);
/* Setup word width */ /* Setup word width */
conf &= ~SPICC_BITLENGTH_MASK; conf &= ~SPICC_BITLENGTH_MASK;
conf |= FIELD_PREP(SPICC_BITLENGTH_MASK, conf |= FIELD_PREP(SPICC_BITLENGTH_MASK,
...@@ -350,6 +352,32 @@ static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc, ...@@ -350,6 +352,32 @@ static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc,
/* Ignore if unchanged */ /* Ignore if unchanged */
if (conf != conf_orig) if (conf != conf_orig)
writel_relaxed(conf, spicc->base + SPICC_CONREG); writel_relaxed(conf, spicc->base + SPICC_CONREG);
clk_set_rate(spicc->clk, xfer->speed_hz);
meson_spicc_auto_io_delay(spicc);
writel_relaxed(0, spicc->base + SPICC_DMAREG);
}
static void meson_spicc_reset_fifo(struct meson_spicc_device *spicc)
{
u32 data;
if (spicc->data->has_oen)
writel_bits_relaxed(SPICC_ENH_MAIN_CLK_AO,
SPICC_ENH_MAIN_CLK_AO,
spicc->base + SPICC_ENH_CTL0);
writel_bits_relaxed(SPICC_FIFORST_W1_MASK, SPICC_FIFORST_W1_MASK,
spicc->base + SPICC_TESTREG);
while (meson_spicc_rxready(spicc))
data = readl_relaxed(spicc->base + SPICC_RXDATA);
if (spicc->data->has_oen)
writel_bits_relaxed(SPICC_ENH_MAIN_CLK_AO, 0,
spicc->base + SPICC_ENH_CTL0);
} }
static int meson_spicc_transfer_one(struct spi_master *master, static int meson_spicc_transfer_one(struct spi_master *master,
...@@ -357,8 +385,6 @@ static int meson_spicc_transfer_one(struct spi_master *master, ...@@ -357,8 +385,6 @@ static int meson_spicc_transfer_one(struct spi_master *master,
struct spi_transfer *xfer) struct spi_transfer *xfer)
{ {
struct meson_spicc_device *spicc = spi_master_get_devdata(master); struct meson_spicc_device *spicc = spi_master_get_devdata(master);
unsigned int burst_len;
u32 irq = 0;
/* Store current transfer */ /* Store current transfer */
spicc->xfer = xfer; spicc->xfer = xfer;
...@@ -372,22 +398,22 @@ static int meson_spicc_transfer_one(struct spi_master *master, ...@@ -372,22 +398,22 @@ static int meson_spicc_transfer_one(struct spi_master *master,
spicc->bytes_per_word = spicc->bytes_per_word =
DIV_ROUND_UP(spicc->xfer->bits_per_word, 8); DIV_ROUND_UP(spicc->xfer->bits_per_word, 8);
if (xfer->len % spicc->bytes_per_word)
return -EINVAL;
/* Setup transfer parameters */ /* Setup transfer parameters */
meson_spicc_setup_xfer(spicc, xfer); meson_spicc_setup_xfer(spicc, xfer);
burst_len = min_t(unsigned int, meson_spicc_reset_fifo(spicc);
spicc->xfer_remain / spicc->bytes_per_word,
SPICC_BURST_MAX);
meson_spicc_setup_burst(spicc, burst_len); /* Setup burst */
meson_spicc_setup_burst(spicc);
irq = meson_spicc_setup_rx_irq(spicc, irq);
/* Start burst */ /* Start burst */
writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG); writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);
/* Enable interrupts */ /* Enable interrupts */
writel_relaxed(irq, spicc->base + SPICC_INTREG); writel_relaxed(SPICC_TC_EN, spicc->base + SPICC_INTREG);
return 1; return 1;
} }
...@@ -444,7 +470,7 @@ static int meson_spicc_prepare_message(struct spi_master *master, ...@@ -444,7 +470,7 @@ static int meson_spicc_prepare_message(struct spi_master *master,
/* Setup no wait cycles by default */ /* Setup no wait cycles by default */
writel_relaxed(0, spicc->base + SPICC_PERIODREG); writel_relaxed(0, spicc->base + SPICC_PERIODREG);
writel_bits_relaxed(BIT(24), BIT(24), spicc->base + SPICC_TESTREG); writel_bits_relaxed(SPICC_LBC_W1, 0, spicc->base + SPICC_TESTREG);
return 0; return 0;
} }
...@@ -456,9 +482,6 @@ static int meson_spicc_unprepare_transfer(struct spi_master *master) ...@@ -456,9 +482,6 @@ static int meson_spicc_unprepare_transfer(struct spi_master *master)
/* Disable all IRQs */ /* Disable all IRQs */
writel(0, spicc->base + SPICC_INTREG); writel(0, spicc->base + SPICC_INTREG);
/* Disable controller */
writel_bits_relaxed(SPICC_ENABLE, 0, spicc->base + SPICC_CONREG);
device_reset_optional(&spicc->pdev->dev); device_reset_optional(&spicc->pdev->dev);
return 0; return 0;
...@@ -477,11 +500,167 @@ static void meson_spicc_cleanup(struct spi_device *spi) ...@@ -477,11 +500,167 @@ static void meson_spicc_cleanup(struct spi_device *spi)
spi->controller_state = NULL; spi->controller_state = NULL;
} }
/*
* The Clock Mux
* x-----------------x x------------x x------\
* |---| pow2 fixed div |---| pow2 div |----| |
* | x-----------------x x------------x | |
* src ---| | mux |-- out
* | x-----------------x x------------x | |
* |---| enh fixed div |---| enh div |0---| |
* x-----------------x x------------x x------/
*
* Clk path for GX series:
* src -> pow2 fixed div -> pow2 div -> out
*
* Clk path for AXG series:
* src -> pow2 fixed div -> pow2 div -> mux -> out
* src -> enh fixed div -> enh div -> mux -> out
*
* Clk path for G12A series:
* pclk -> pow2 fixed div -> pow2 div -> mux -> out
* pclk -> enh fixed div -> enh div -> mux -> out
*/
static int meson_spicc_clk_init(struct meson_spicc_device *spicc)
{
struct device *dev = &spicc->pdev->dev;
struct clk_fixed_factor *pow2_fixed_div, *enh_fixed_div;
struct clk_divider *pow2_div, *enh_div;
struct clk_mux *mux;
struct clk_init_data init;
struct clk *clk;
struct clk_parent_data parent_data[2];
char name[64];
memset(&init, 0, sizeof(init));
memset(&parent_data, 0, sizeof(parent_data));
init.parent_data = parent_data;
/* algorithm for pow2 div: rate = freq / 4 / (2 ^ N) */
pow2_fixed_div = devm_kzalloc(dev, sizeof(*pow2_fixed_div), GFP_KERNEL);
if (!pow2_fixed_div)
return -ENOMEM;
snprintf(name, sizeof(name), "%s#pow2_fixed_div", dev_name(dev));
init.name = name;
init.ops = &clk_fixed_factor_ops;
init.flags = 0;
if (spicc->data->has_pclk)
parent_data[0].hw = __clk_get_hw(spicc->pclk);
else
parent_data[0].hw = __clk_get_hw(spicc->core);
init.num_parents = 1;
pow2_fixed_div->mult = 1,
pow2_fixed_div->div = 4,
pow2_fixed_div->hw.init = &init;
clk = devm_clk_register(dev, &pow2_fixed_div->hw);
if (WARN_ON(IS_ERR(clk)))
return PTR_ERR(clk);
pow2_div = devm_kzalloc(dev, sizeof(*pow2_div), GFP_KERNEL);
if (!pow2_div)
return -ENOMEM;
snprintf(name, sizeof(name), "%s#pow2_div", dev_name(dev));
init.name = name;
init.ops = &clk_divider_ops;
init.flags = CLK_SET_RATE_PARENT;
parent_data[0].hw = &pow2_fixed_div->hw;
init.num_parents = 1;
pow2_div->shift = 16,
pow2_div->width = 3,
pow2_div->flags = CLK_DIVIDER_POWER_OF_TWO,
pow2_div->reg = spicc->base + SPICC_CONREG;
pow2_div->hw.init = &init;
clk = devm_clk_register(dev, &pow2_div->hw);
if (WARN_ON(IS_ERR(clk)))
return PTR_ERR(clk);
if (!spicc->data->has_enhance_clk_div) {
spicc->clk = clk;
return 0;
}
/* algorithm for enh div: rate = freq / 2 / (N + 1) */
enh_fixed_div = devm_kzalloc(dev, sizeof(*enh_fixed_div), GFP_KERNEL);
if (!enh_fixed_div)
return -ENOMEM;
snprintf(name, sizeof(name), "%s#enh_fixed_div", dev_name(dev));
init.name = name;
init.ops = &clk_fixed_factor_ops;
init.flags = 0;
if (spicc->data->has_pclk)
parent_data[0].hw = __clk_get_hw(spicc->pclk);
else
parent_data[0].hw = __clk_get_hw(spicc->core);
init.num_parents = 1;
enh_fixed_div->mult = 1,
enh_fixed_div->div = 2,
enh_fixed_div->hw.init = &init;
clk = devm_clk_register(dev, &enh_fixed_div->hw);
if (WARN_ON(IS_ERR(clk)))
return PTR_ERR(clk);
enh_div = devm_kzalloc(dev, sizeof(*enh_div), GFP_KERNEL);
if (!enh_div)
return -ENOMEM;
snprintf(name, sizeof(name), "%s#enh_div", dev_name(dev));
init.name = name;
init.ops = &clk_divider_ops;
init.flags = CLK_SET_RATE_PARENT;
parent_data[0].hw = &enh_fixed_div->hw;
init.num_parents = 1;
enh_div->shift = 16,
enh_div->width = 8,
enh_div->reg = spicc->base + SPICC_ENH_CTL0;
enh_div->hw.init = &init;
clk = devm_clk_register(dev, &enh_div->hw);
if (WARN_ON(IS_ERR(clk)))
return PTR_ERR(clk);
mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
if (!mux)
return -ENOMEM;
snprintf(name, sizeof(name), "%s#sel", dev_name(dev));
init.name = name;
init.ops = &clk_mux_ops;
parent_data[0].hw = &pow2_div->hw;
parent_data[1].hw = &enh_div->hw;
init.num_parents = 2;
init.flags = CLK_SET_RATE_PARENT;
mux->mask = 0x1,
mux->shift = 24,
mux->reg = spicc->base + SPICC_ENH_CTL0;
mux->hw.init = &init;
spicc->clk = devm_clk_register(dev, &mux->hw);
if (WARN_ON(IS_ERR(spicc->clk)))
return PTR_ERR(spicc->clk);
return 0;
}
static int meson_spicc_probe(struct platform_device *pdev) static int meson_spicc_probe(struct platform_device *pdev)
{ {
struct spi_master *master; struct spi_master *master;
struct meson_spicc_device *spicc; struct meson_spicc_device *spicc;
int ret, irq, rate; int ret, irq;
master = spi_alloc_master(&pdev->dev, sizeof(*spicc)); master = spi_alloc_master(&pdev->dev, sizeof(*spicc));
if (!master) { if (!master) {
...@@ -491,6 +670,13 @@ static int meson_spicc_probe(struct platform_device *pdev) ...@@ -491,6 +670,13 @@ static int meson_spicc_probe(struct platform_device *pdev)
spicc = spi_master_get_devdata(master); spicc = spi_master_get_devdata(master);
spicc->master = master; spicc->master = master;
spicc->data = of_device_get_match_data(&pdev->dev);
if (!spicc->data) {
dev_err(&pdev->dev, "failed to get match data\n");
ret = -EINVAL;
goto out_master;
}
spicc->pdev = pdev; spicc->pdev = pdev;
platform_set_drvdata(pdev, spicc); platform_set_drvdata(pdev, spicc);
...@@ -501,6 +687,10 @@ static int meson_spicc_probe(struct platform_device *pdev) ...@@ -501,6 +687,10 @@ static int meson_spicc_probe(struct platform_device *pdev)
goto out_master; goto out_master;
} }
/* Set master mode and enable controller */
writel_relaxed(SPICC_ENABLE | SPICC_MODE_MASTER,
spicc->base + SPICC_CONREG);
/* Disable all IRQs */ /* Disable all IRQs */
writel_relaxed(0, spicc->base + SPICC_INTREG); writel_relaxed(0, spicc->base + SPICC_INTREG);
...@@ -519,12 +709,26 @@ static int meson_spicc_probe(struct platform_device *pdev) ...@@ -519,12 +709,26 @@ static int meson_spicc_probe(struct platform_device *pdev)
goto out_master; goto out_master;
} }
if (spicc->data->has_pclk) {
spicc->pclk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(spicc->pclk)) {
dev_err(&pdev->dev, "pclk clock request failed\n");
ret = PTR_ERR(spicc->pclk);
goto out_master;
}
}
ret = clk_prepare_enable(spicc->core); ret = clk_prepare_enable(spicc->core);
if (ret) { if (ret) {
dev_err(&pdev->dev, "core clock enable failed\n"); dev_err(&pdev->dev, "core clock enable failed\n");
goto out_master; goto out_master;
} }
rate = clk_get_rate(spicc->core);
ret = clk_prepare_enable(spicc->pclk);
if (ret) {
dev_err(&pdev->dev, "pclk clock enable failed\n");
goto out_master;
}
device_reset_optional(&pdev->dev); device_reset_optional(&pdev->dev);
...@@ -536,7 +740,8 @@ static int meson_spicc_probe(struct platform_device *pdev) ...@@ -536,7 +740,8 @@ static int meson_spicc_probe(struct platform_device *pdev)
SPI_BPW_MASK(16) | SPI_BPW_MASK(16) |
SPI_BPW_MASK(8); SPI_BPW_MASK(8);
master->flags = (SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX); master->flags = (SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX);
master->min_speed_hz = rate >> 9; master->min_speed_hz = spicc->data->min_speed_hz;
master->max_speed_hz = spicc->data->max_speed_hz;
master->setup = meson_spicc_setup; master->setup = meson_spicc_setup;
master->cleanup = meson_spicc_cleanup; master->cleanup = meson_spicc_cleanup;
master->prepare_message = meson_spicc_prepare_message; master->prepare_message = meson_spicc_prepare_message;
...@@ -544,11 +749,13 @@ static int meson_spicc_probe(struct platform_device *pdev) ...@@ -544,11 +749,13 @@ static int meson_spicc_probe(struct platform_device *pdev)
master->transfer_one = meson_spicc_transfer_one; master->transfer_one = meson_spicc_transfer_one;
master->use_gpio_descriptors = true; master->use_gpio_descriptors = true;
/* Setup max rate according to the Meson GX datasheet */ meson_spicc_oen_enable(spicc);
if ((rate >> 2) > SPICC_MAX_FREQ)
master->max_speed_hz = SPICC_MAX_FREQ; ret = meson_spicc_clk_init(spicc);
else if (ret) {
master->max_speed_hz = rate >> 2; dev_err(&pdev->dev, "clock registration failed\n");
goto out_master;
}
ret = devm_spi_register_master(&pdev->dev, master); ret = devm_spi_register_master(&pdev->dev, master);
if (ret) { if (ret) {
...@@ -560,6 +767,7 @@ static int meson_spicc_probe(struct platform_device *pdev) ...@@ -560,6 +767,7 @@ static int meson_spicc_probe(struct platform_device *pdev)
out_clk: out_clk:
clk_disable_unprepare(spicc->core); clk_disable_unprepare(spicc->core);
clk_disable_unprepare(spicc->pclk);
out_master: out_master:
spi_master_put(master); spi_master_put(master);
...@@ -575,13 +783,47 @@ static int meson_spicc_remove(struct platform_device *pdev) ...@@ -575,13 +783,47 @@ static int meson_spicc_remove(struct platform_device *pdev)
writel(0, spicc->base + SPICC_CONREG); writel(0, spicc->base + SPICC_CONREG);
clk_disable_unprepare(spicc->core); clk_disable_unprepare(spicc->core);
clk_disable_unprepare(spicc->pclk);
return 0; return 0;
} }
static const struct meson_spicc_data meson_spicc_gx_data = {
.max_speed_hz = 30000000,
.min_speed_hz = 325000,
.fifo_size = 16,
};
static const struct meson_spicc_data meson_spicc_axg_data = {
.max_speed_hz = 80000000,
.min_speed_hz = 325000,
.fifo_size = 16,
.has_oen = true,
.has_enhance_clk_div = true,
};
static const struct meson_spicc_data meson_spicc_g12a_data = {
.max_speed_hz = 166666666,
.min_speed_hz = 50000,
.fifo_size = 15,
.has_oen = true,
.has_enhance_clk_div = true,
.has_pclk = true,
};
static const struct of_device_id meson_spicc_of_match[] = { static const struct of_device_id meson_spicc_of_match[] = {
{ .compatible = "amlogic,meson-gx-spicc", }, {
{ .compatible = "amlogic,meson-axg-spicc", }, .compatible = "amlogic,meson-gx-spicc",
.data = &meson_spicc_gx_data,
},
{
.compatible = "amlogic,meson-axg-spicc",
.data = &meson_spicc_axg_data,
},
{
.compatible = "amlogic,meson-g12a-spicc",
.data = &meson_spicc_g12a_data,
},
{ /* sentinel */ } { /* sentinel */ }
}; };
MODULE_DEVICE_TABLE(of, meson_spicc_of_match); MODULE_DEVICE_TABLE(of, meson_spicc_of_match);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment