Unverified Commit 73156083 authored by Mark Brown's avatar Mark Brown
Browse files

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
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ properties:
    enum:
      - 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-g12a-spicc # SPICC controller on Amlogic G12A and compatible SoCs

  interrupts:
    maxItems: 1
@@ -40,6 +41,27 @@ properties:
    items:
      - 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:
  - compatible
  - reg
+1 −0
Original line number Diff line number Diff line
@@ -428,6 +428,7 @@ config SPI_FSL_ESPI

config SPI_MESON_SPICC
	tristate "Amlogic Meson SPICC controller"
	depends on COMMON_CLK
	depends on ARCH_MESON || COMPILE_TEST
	help
	  This enables master mode support for the SPICC (SPI communication
+369 −127
Original line number Diff line number Diff line
@@ -9,11 +9,13 @@

#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
@@ -33,7 +35,6 @@
 *   to have a CS go down over the full transfer
 */

#define SPICC_MAX_FREQ	30000000
#define SPICC_MAX_BURST	128

/* Register Map */
@@ -105,7 +106,21 @@
#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_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_W1_MASK	GENMASK(23, 22) /* FIFO Softreset Write-Only */

@@ -113,31 +128,59 @@

#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) \
	writel_relaxed((readl_relaxed(addr) & ~(mask)) | (val), addr)

#define SPICC_BURST_MAX	16
#define SPICC_FIFO_HALF 10
struct meson_spicc_data {
	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 spi_master		*master;
	struct platform_device		*pdev;
	void __iomem			*base;
	struct clk			*core;
	struct clk			*pclk;
	struct clk			*clk;
	struct spi_message		*message;
	struct spi_transfer		*xfer;
	const struct meson_spicc_data	*data;
	u8				*tx_buf;
	u8				*rx_buf;
	unsigned int			bytes_per_word;
	unsigned long			tx_remain;
	unsigned long			txb_remain;
	unsigned long			rx_remain;
	unsigned long			rxb_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)
{
	return !!FIELD_GET(SPICC_TF,
@@ -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)
{
	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));
}

@@ -201,34 +244,22 @@ static inline void meson_spicc_tx(struct meson_spicc_device *spicc)
			       spicc->base + SPICC_TXDATA);
}

static inline u32 meson_spicc_setup_rx_irq(struct meson_spicc_device *spicc,
					   u32 irq_ctrl)
static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc)
{
	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)
{
	unsigned int burst_len = min_t(unsigned int,
				       spicc->xfer_remain /
				       spicc->bytes_per_word,
				       spicc->data->fifo_size);
	/* Setup Xfer variables */
	spicc->tx_remain = burst_len;
	spicc->rx_remain = burst_len;
	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 */
	writel_bits_relaxed(SPICC_BURSTLENGTH_MASK,
			FIELD_PREP(SPICC_BURSTLENGTH_MASK,
				burst_len),
				burst_len - 1),
			spicc->base + SPICC_CONREG);

	/* Fill TX FIFO */
@@ -238,36 +269,13 @@ static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc,
static irqreturn_t meson_spicc_irq(int irq, 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 */
	meson_spicc_rx(spicc);

	/* Enable TC interrupt since we transferred everything */
	if (!spicc->tx_remain && !spicc->rx_remain) {
		spicc->is_burst_end = true;

		/* Enable TC interrupt */
		ctrl |= SPICC_TC_EN;

		/* Reload IRQ status */
		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) {
	if (!spicc->xfer_remain) {
		/* Disable all IRQs */
		writel(0, spicc->base + SPICC_INTREG);

@@ -276,59 +284,56 @@ static irqreturn_t meson_spicc_irq(int irq, void *data)
		return IRQ_HANDLED;
	}

		burst_len = min_t(unsigned int,
				  spicc->xfer_remain / spicc->bytes_per_word,
				  SPICC_BURST_MAX);

	/* Setup burst */
		meson_spicc_setup_burst(spicc, burst_len);
	meson_spicc_setup_burst(spicc);

		/* Restart burst */
		writel_bits_relaxed(SPICC_XCH, SPICC_XCH,
				    spicc->base + SPICC_CONREG);
	}

	/* Setup RX interrupt trigger */
	ctrl = meson_spicc_setup_rx_irq(spicc, ctrl);

	/* Reconfigure interrupts */
	writel(ctrl, spicc->base + SPICC_INTREG);
	/* Start burst */
	writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);

	return IRQ_HANDLED;
}

static u32 meson_spicc_setup_speed(struct meson_spicc_device *spicc, u32 conf,
				   u32 speed)
static void meson_spicc_auto_io_delay(struct meson_spicc_device *spicc)
{
	unsigned long parent, value;
	unsigned int i, div;

	parent = clk_get_rate(spicc->core);

	/* Find closest inferior/equal possible speed */
	for (i = 0 ; i < 7 ; ++i) {
		/* 2^(data_rate+2) */
		value = parent >> (i + 2);

		if (value <= speed)
			break;
	u32 div, hz;
	u32 mi_delay, cap_delay;
	u32 conf;

	if (spicc->data->has_enhance_clk_div) {
		div = FIELD_GET(SPICC_ENH_DATARATE_MASK,
				readl_relaxed(spicc->base + SPICC_ENH_CTL0));
		div++;
		div <<= 1;
	} else {
		div = FIELD_GET(SPICC_DATARATE_MASK,
				readl_relaxed(spicc->base + SPICC_CONREG));
		div += 2;
		div = 1 << div;
	}

	/* If provided speed it lower than max divider, use max divider */
	if (i > 7) {
		div = 7;
		dev_warn_once(&spicc->pdev->dev, "unable to get close to speed %u\n",
			      speed);
	} else
		div = i;

	dev_dbg(&spicc->pdev->dev, "parent %lu, speed %u -> %lu (%u)\n",
		parent, speed, value, div);

	conf &= ~SPICC_DATARATE_MASK;
	conf |= FIELD_PREP(SPICC_DATARATE_MASK, div);

	return conf;
	mi_delay = SPICC_MI_NO_DELAY;
	cap_delay = SPICC_CAP_AHEAD_2_CYCLE;
	hz = clk_get_rate(spicc->clk);

	if (hz >= 100000000)
		cap_delay = SPICC_CAP_DELAY_1_CYCLE;
	else if (hz >= 80000000)
		cap_delay = SPICC_CAP_NO_DELAY;
	else if (hz >= 40000000)
		cap_delay = SPICC_CAP_AHEAD_1_CYCLE;
	else if (div >= 16)
		mi_delay = SPICC_MI_DELAY_3_CYCLE;
	else if (div >= 8)
		mi_delay = SPICC_MI_DELAY_2_CYCLE;
	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,
@@ -339,9 +344,6 @@ static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc,
	/* Read original configuration */
	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 */
	conf &= ~SPICC_BITLENGTH_MASK;
	conf |= FIELD_PREP(SPICC_BITLENGTH_MASK,
@@ -350,6 +352,32 @@ static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc,
	/* Ignore if unchanged */
	if (conf != conf_orig)
		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,
@@ -357,8 +385,6 @@ static int meson_spicc_transfer_one(struct spi_master *master,
				    struct spi_transfer *xfer)
{
	struct meson_spicc_device *spicc = spi_master_get_devdata(master);
	unsigned int burst_len;
	u32 irq = 0;

	/* Store current transfer */
	spicc->xfer = xfer;
@@ -372,22 +398,22 @@ static int meson_spicc_transfer_one(struct spi_master *master,
	spicc->bytes_per_word =
	   DIV_ROUND_UP(spicc->xfer->bits_per_word, 8);

	if (xfer->len % spicc->bytes_per_word)
		return -EINVAL;

	/* Setup transfer parameters */
	meson_spicc_setup_xfer(spicc, xfer);

	burst_len = min_t(unsigned int,
			  spicc->xfer_remain / spicc->bytes_per_word,
			  SPICC_BURST_MAX);
	meson_spicc_reset_fifo(spicc);

	meson_spicc_setup_burst(spicc, burst_len);

	irq = meson_spicc_setup_rx_irq(spicc, irq);
	/* Setup burst */
	meson_spicc_setup_burst(spicc);

	/* Start burst */
	writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);

	/* Enable interrupts */
	writel_relaxed(irq, spicc->base + SPICC_INTREG);
	writel_relaxed(SPICC_TC_EN, spicc->base + SPICC_INTREG);

	return 1;
}
@@ -444,7 +470,7 @@ static int meson_spicc_prepare_message(struct spi_master *master,
	/* Setup no wait cycles by default */
	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;
}
@@ -456,9 +482,6 @@ static int meson_spicc_unprepare_transfer(struct spi_master *master)
	/* Disable all IRQs */
	writel(0, spicc->base + SPICC_INTREG);

	/* Disable controller */
	writel_bits_relaxed(SPICC_ENABLE, 0, spicc->base + SPICC_CONREG);

	device_reset_optional(&spicc->pdev->dev);

	return 0;
@@ -477,11 +500,167 @@ static void meson_spicc_cleanup(struct spi_device *spi)
	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)
{
	struct spi_master *master;
	struct meson_spicc_device *spicc;
	int ret, irq, rate;
	int ret, irq;

	master = spi_alloc_master(&pdev->dev, sizeof(*spicc));
	if (!master) {
@@ -491,6 +670,13 @@ static int meson_spicc_probe(struct platform_device *pdev)
	spicc = spi_master_get_devdata(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;
	platform_set_drvdata(pdev, spicc);

@@ -501,6 +687,10 @@ static int meson_spicc_probe(struct platform_device *pdev)
		goto out_master;
	}

	/* Set master mode and enable controller */
	writel_relaxed(SPICC_ENABLE | SPICC_MODE_MASTER,
		       spicc->base + SPICC_CONREG);

	/* Disable all IRQs */
	writel_relaxed(0, spicc->base + SPICC_INTREG);

@@ -519,12 +709,26 @@ static int meson_spicc_probe(struct platform_device *pdev)
		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);
	if (ret) {
		dev_err(&pdev->dev, "core clock enable failed\n");
		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);

@@ -536,7 +740,8 @@ static int meson_spicc_probe(struct platform_device *pdev)
				     SPI_BPW_MASK(16) |
				     SPI_BPW_MASK(8);
	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->cleanup = meson_spicc_cleanup;
	master->prepare_message = meson_spicc_prepare_message;
@@ -544,11 +749,13 @@ static int meson_spicc_probe(struct platform_device *pdev)
	master->transfer_one = meson_spicc_transfer_one;
	master->use_gpio_descriptors = true;

	/* Setup max rate according to the Meson GX datasheet */
	if ((rate >> 2) > SPICC_MAX_FREQ)
		master->max_speed_hz = SPICC_MAX_FREQ;
	else
		master->max_speed_hz = rate >> 2;
	meson_spicc_oen_enable(spicc);

	ret = meson_spicc_clk_init(spicc);
	if (ret) {
		dev_err(&pdev->dev, "clock registration failed\n");
		goto out_master;
	}

	ret = devm_spi_register_master(&pdev->dev, master);
	if (ret) {
@@ -560,6 +767,7 @@ static int meson_spicc_probe(struct platform_device *pdev)

out_clk:
	clk_disable_unprepare(spicc->core);
	clk_disable_unprepare(spicc->pclk);

out_master:
	spi_master_put(master);
@@ -575,13 +783,47 @@ static int meson_spicc_remove(struct platform_device *pdev)
	writel(0, spicc->base + SPICC_CONREG);

	clk_disable_unprepare(spicc->core);
	clk_disable_unprepare(spicc->pclk);

	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[] = {
	{ .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 */ }
};
MODULE_DEVICE_TABLE(of, meson_spicc_of_match);