Commit e4bf1b09 authored by Martin Blumenstingl's avatar Martin Blumenstingl Committed by Ulf Hansson
Browse files

mmc: host: meson-mx-sdhc: new driver for the Amlogic Meson SDHC host



The SDHC MMC host controller on Amlogic SoCs provides an eMMC and MMC
card interface with 1/4/8-bit bus width.
It supports eMMC spec 4.4x/4.5x including HS200 (up to 100MHz clock).

The public S805 datasheet [0] contains a short documentation about the
registers. Unfortunately it does not describe how to use the registers
to make the hardware work. Thus this driver is based on reading (and
understanding) the Amlogic 3.10 GPL kernel code.

Some hardware details are not easy to see. Jianxin Pan was kind enough
to answer my questions:
The hardware has built-in busy timeout support. The maximum timeout is
30 seconds. This is only documented in Amlogic's internal
documentation.

The controller only works with very specific clock configurations. The
details are not part of the public datasheet. In my own words the
supported configurations are:
- 399.812kHz:	clkin =  850MHz div = 2126 sd_rx_phase = 63
- 1MHz:		clkin =  850MHz div = 850  sd_rx_phase = 55
- 5.986MHz:	clkin =  850MHz div = 142  sd_rx_phase = 24
- 25MHz:	clkin =  850MHz div = 34   sd_rx_phase = 15
- 47.222MHz:	clkin =  850MHz div = 18   sd_rx_phase = 11/15 (SDR50/HS)
- 53.125MHz:	clkin =  850MHz div = 16   sd_rx_phase = (tuning)
- 70.833MHz:	clkin =  850MHz div = 12   sd_rx_phase = (tuning)
- 85MHz:	clkin =  850MHz div = 10   sd_rx_phase = (tuning)
- 94.44MHz:	clkin =  850MHz div = 9    sd_rx_phase = (tuning)
- 106.25MHz:	clkin =  850MHz div = 8    sd_rx_phase = (tuning)
- 127.5MHz:     clkin = 1275MHz div = 10   sd_rx_phase = (tuning)
- 141.667MHz:   clkin =  850MHz div = 6    sd_rx_phase = (tuning)
- 159.375MHz:	clkin = 1275MHz div = 8    sd_rx_phase = (tuning)
- 212.5MHz:	clkin = 1275MHz div = 6    sd_rx_phase = (tuning)
- (sd_tx_phase is always 1, 94.44MHz is not listed in the datasheet
   but this is what the 3.10 BSP kernel on Odroid-C1 actually uses)

NOTE: CMD23 support is disabled for now because it results in command
timeouts and thus decreases read performance.

Tested-by: default avatarWei Wang <lnykww@gmail.com>
Tested-by: default avatarXin Yin <yinxin_1989@aliyun.com>
Reviewed-by: default avatarXin Yin <yinxin_1989@aliyun.com>
Tested-by: default avatarAnand Moon <linux.amoon@gmail.com>
Signed-off-by: default avatarMartin Blumenstingl <martin.blumenstingl@googlemail.com>
Reviewed-by: default avatarJerome Brunet <jbrunet@baylibre.com>
Link: https://lore.kernel.org/r/20200512204147.504087-3-martin.blumenstingl@googlemail.com


Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent e5f31378
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -405,6 +405,20 @@ config MMC_MESON_GX

	  If you have a controller with this interface, say Y here.

config MMC_MESON_MX_SDHC
	tristate "Amlogic Meson SDHC Host Controller support"
	depends on (ARM && ARCH_MESON) || COMPILE_TEST
	depends on COMMON_CLK
	depends on OF
	help
	  This selects support for the SDHC Host Controller on
	  Amlogic Meson6, Meson8, Meson8b and Meson8m2 SoCs.
	  The controller supports the SD/SDIO Spec 3.x and eMMC Spec 4.5x
	  with 1, 4, and 8 bit bus widths.

	  If you have a controller with this interface, say Y or M here.
	  If unsure, say N.

config MMC_MESON_MX_SDIO
	tristate "Amlogic Meson6/Meson8/Meson8b SD/MMC Host Controller support"
	depends on ARCH_MESON || COMPILE_TEST
+1 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ obj-$(CONFIG_MMC_VUB300) += vub300.o
obj-$(CONFIG_MMC_USHC)		+= ushc.o
obj-$(CONFIG_MMC_WMT)		+= wmt-sdmmc.o
obj-$(CONFIG_MMC_MESON_GX)	+= meson-gx-mmc.o
obj-$(CONFIG_MMC_MESON_MX_SDHC)	+= meson-mx-sdhc-clkc.o meson-mx-sdhc.o
obj-$(CONFIG_MMC_MESON_MX_SDIO)	+= meson-mx-sdio.o
obj-$(CONFIG_MMC_MOXART)	+= moxart-mmc.o
obj-$(CONFIG_MMC_SUNXI)		+= sunxi-mmc.o
+158 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0+
/*
 * Amlogic Meson SDHC clock controller
 *
 * Copyright (C) 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
 */

#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/platform_device.h>

#include "meson-mx-sdhc.h"

#define MESON_SDHC_NUM_BUILTIN_CLKS	6

struct meson_mx_sdhc_clkc {
	struct clk_mux			src_sel;
	struct clk_divider		div;
	struct clk_gate			mod_clk_en;
	struct clk_gate			tx_clk_en;
	struct clk_gate			rx_clk_en;
	struct clk_gate			sd_clk_en;
};

static const struct clk_parent_data meson_mx_sdhc_src_sel_parents[4] = {
	{ .fw_name = "clkin0" },
	{ .fw_name = "clkin1" },
	{ .fw_name = "clkin2" },
	{ .fw_name = "clkin3" },
};

static const struct clk_div_table meson_mx_sdhc_div_table[] = {
	{ .div = 6, .val = 5, },
	{ .div = 8, .val = 7, },
	{ .div = 9, .val = 8, },
	{ .div = 10, .val = 9, },
	{ .div = 12, .val = 11, },
	{ .div = 16, .val = 15, },
	{ .div = 18, .val = 17, },
	{ .div = 34, .val = 33, },
	{ .div = 142, .val = 141, },
	{ .div = 850, .val = 849, },
	{ .div = 2126, .val = 2125, },
	{ .div = 4096, .val = 4095, },
	{ /* sentinel */ }
};

static int meson_mx_sdhc_clk_hw_register(struct device *dev,
					 const char *name_suffix,
					 const struct clk_parent_data *parents,
					 unsigned int num_parents,
					 const struct clk_ops *ops,
					 struct clk_hw *hw)
{
	struct clk_init_data init = { 0 };
	char clk_name[32];

	snprintf(clk_name, sizeof(clk_name), "%s#%s", dev_name(dev),
		 name_suffix);

	init.name = clk_name;
	init.ops = ops;
	init.flags = CLK_SET_RATE_PARENT;
	init.parent_data = parents;
	init.num_parents = num_parents;

	hw->init = &init;

	return devm_clk_hw_register(dev, hw);
}

static int meson_mx_sdhc_gate_clk_hw_register(struct device *dev,
					      const char *name_suffix,
					      struct clk_hw *parent,
					      struct clk_hw *hw)
{
	struct clk_parent_data parent_data = { .hw = parent };

	return meson_mx_sdhc_clk_hw_register(dev, name_suffix, &parent_data, 1,
					     &clk_gate_ops, hw);
}

int meson_mx_sdhc_register_clkc(struct device *dev, void __iomem *base,
				struct clk_bulk_data *clk_bulk_data)
{
	struct clk_parent_data div_parent = { 0 };
	struct meson_mx_sdhc_clkc *clkc_data;
	int ret;

	clkc_data = devm_kzalloc(dev, sizeof(*clkc_data), GFP_KERNEL);
	if (!clkc_data)
		return -ENOMEM;

	clkc_data->src_sel.reg = base + MESON_SDHC_CLKC;
	clkc_data->src_sel.mask = 0x3;
	clkc_data->src_sel.shift = 16;
	ret = meson_mx_sdhc_clk_hw_register(dev, "src_sel",
					    meson_mx_sdhc_src_sel_parents, 4,
					    &clk_mux_ops,
					    &clkc_data->src_sel.hw);
	if (ret)
		return ret;

	clkc_data->div.reg = base + MESON_SDHC_CLKC;
	clkc_data->div.shift = 0;
	clkc_data->div.width = 12;
	clkc_data->div.table = meson_mx_sdhc_div_table;
	div_parent.hw = &clkc_data->src_sel.hw;
	ret = meson_mx_sdhc_clk_hw_register(dev, "div", &div_parent, 1,
					    &clk_divider_ops,
					    &clkc_data->div.hw);
	if (ret)
		return ret;

	clkc_data->mod_clk_en.reg = base + MESON_SDHC_CLKC;
	clkc_data->mod_clk_en.bit_idx = 15;
	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "mod_clk_on",
						 &clkc_data->div.hw,
						 &clkc_data->mod_clk_en.hw);
	if (ret)
		return ret;

	clkc_data->tx_clk_en.reg = base + MESON_SDHC_CLKC;
	clkc_data->tx_clk_en.bit_idx = 14;
	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "tx_clk_on",
						 &clkc_data->div.hw,
						 &clkc_data->tx_clk_en.hw);
	if (ret)
		return ret;

	clkc_data->rx_clk_en.reg = base + MESON_SDHC_CLKC;
	clkc_data->rx_clk_en.bit_idx = 13;
	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "rx_clk_on",
						 &clkc_data->div.hw,
						 &clkc_data->rx_clk_en.hw);
	if (ret)
		return ret;

	clkc_data->sd_clk_en.reg = base + MESON_SDHC_CLKC;
	clkc_data->sd_clk_en.bit_idx = 12;
	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "sd_clk_on",
						 &clkc_data->div.hw,
						 &clkc_data->sd_clk_en.hw);
	if (ret)
		return ret;

	/*
	 * TODO: Replace clk_hw.clk with devm_clk_hw_get_clk() once that is
	 * available.
	 */
	clk_bulk_data[0].clk = clkc_data->mod_clk_en.hw.clk;
	clk_bulk_data[1].clk = clkc_data->sd_clk_en.hw.clk;
	clk_bulk_data[2].clk = clkc_data->tx_clk_en.hw.clk;
	clk_bulk_data[3].clk = clkc_data->rx_clk_en.hw.clk;

	return 0;
}
+907 −0

File added.

Preview size limit exceeded, changes collapsed.

+141 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * Copyright (C) 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
 */

#ifndef _MESON_MX_SDHC_H_
#define _MESON_MX_SDHC_H_

#include <linux/bitfield.h>

#define MESON_SDHC_ARGU						0x00

#define MESON_SDHC_SEND						0x04
	#define MESON_SDHC_SEND_CMD_INDEX			GENMASK(5, 0)
	#define MESON_SDHC_SEND_CMD_HAS_RESP			BIT(6)
	#define MESON_SDHC_SEND_CMD_HAS_DATA			BIT(7)
	#define MESON_SDHC_SEND_RESP_LEN			BIT(8)
	#define MESON_SDHC_SEND_RESP_NO_CRC			BIT(9)
	#define MESON_SDHC_SEND_DATA_DIR			BIT(10)
	#define MESON_SDHC_SEND_DATA_STOP			BIT(11)
	#define MESON_SDHC_SEND_R1B				BIT(12)
	#define MESON_SDHC_SEND_TOTAL_PACK			GENMASK(31, 16)

#define MESON_SDHC_CTRL						0x08
	#define MESON_SDHC_CTRL_DAT_TYPE			GENMASK(1, 0)
	#define MESON_SDHC_CTRL_DDR_MODE			BIT(2)
	#define MESON_SDHC_CTRL_TX_CRC_NOCHECK			BIT(3)
	#define MESON_SDHC_CTRL_PACK_LEN			GENMASK(12, 4)
	#define MESON_SDHC_CTRL_RX_TIMEOUT			GENMASK(19, 13)
	#define MESON_SDHC_CTRL_RX_PERIOD			GENMASK(23, 20)
	#define MESON_SDHC_CTRL_RX_ENDIAN			GENMASK(26, 24)
	#define MESON_SDHC_CTRL_SDIO_IRQ_MODE			BIT(27)
	#define MESON_SDHC_CTRL_DAT0_IRQ_SEL			BIT(28)
	#define MESON_SDHC_CTRL_TX_ENDIAN			GENMASK(31, 29)

#define MESON_SDHC_STAT						0x0c
	#define MESON_SDHC_STAT_CMD_BUSY			BIT(0)
	#define MESON_SDHC_STAT_DAT3_0				GENMASK(4, 1)
	#define MESON_SDHC_STAT_CMD				BIT(5)
	#define MESON_SDHC_STAT_RXFIFO_CNT			GENMASK(12, 6)
	#define MESON_SDHC_STAT_TXFIFO_CNT			GENMASK(19, 13)
	#define MESON_SDHC_STAT_DAT7_4				GENMASK(23, 20)

#define MESON_SDHC_CLKC						0x10
	#define MESON_SDHC_CLKC_CLK_DIV				GENMASK(11, 0)
	#define MESON_SDHC_CLKC_CLK_JIC				BIT(24)
	#define MESON_SDHC_CLKC_MEM_PWR_OFF			GENMASK(26, 25)

#define MESON_SDHC_ADDR						0x14

#define MESON_SDHC_PDMA						0x18
	#define MESON_SDHC_PDMA_DMA_MODE			BIT(0)
	#define MESON_SDHC_PDMA_PIO_RDRESP			GENMASK(3, 1)
	#define MESON_SDHC_PDMA_DMA_URGENT			BIT(4)
	#define MESON_SDHC_PDMA_WR_BURST			GENMASK(9, 5)
	#define MESON_SDHC_PDMA_RD_BURST			GENMASK(14, 10)
	#define MESON_SDHC_PDMA_RXFIFO_TH			GENMASK(21, 15)
	#define MESON_SDHC_PDMA_TXFIFO_TH			GENMASK(28, 22)
	#define MESON_SDHC_PDMA_RXFIFO_MANUAL_FLUSH		GENMASK(30, 29)
	#define MESON_SDHC_PDMA_TXFIFO_FILL			BIT(31)

#define MESON_SDHC_MISC						0x1c
	#define MESON_SDHC_MISC_WCRC_ERR_PATT			GENMASK(6, 4)
	#define MESON_SDHC_MISC_WCRC_OK_PATT			GENMASK(9, 7)
	#define MESON_SDHC_MISC_BURST_NUM			GENMASK(21, 16)
	#define MESON_SDHC_MISC_THREAD_ID			GENMASK(27, 22)
	#define MESON_SDHC_MISC_MANUAL_STOP			BIT(28)
	#define MESON_SDHC_MISC_TXSTART_THRES			GENMASK(31, 29)

#define MESON_SDHC_DATA						0x20

#define MESON_SDHC_ICTL						0x24
	#define MESON_SDHC_ICTL_RESP_OK				BIT(0)
	#define MESON_SDHC_ICTL_RESP_TIMEOUT			BIT(1)
	#define MESON_SDHC_ICTL_RESP_ERR_CRC			BIT(2)
	#define MESON_SDHC_ICTL_RESP_OK_NOCLEAR			BIT(3)
	#define MESON_SDHC_ICTL_DATA_1PACK_OK			BIT(4)
	#define MESON_SDHC_ICTL_DATA_TIMEOUT			BIT(5)
	#define MESON_SDHC_ICTL_DATA_ERR_CRC			BIT(6)
	#define MESON_SDHC_ICTL_DATA_XFER_OK			BIT(7)
	#define MESON_SDHC_ICTL_RX_HIGHER			BIT(8)
	#define MESON_SDHC_ICTL_RX_LOWER			BIT(9)
	#define MESON_SDHC_ICTL_DAT1_IRQ			BIT(10)
	#define MESON_SDHC_ICTL_DMA_DONE			BIT(11)
	#define MESON_SDHC_ICTL_RXFIFO_FULL			BIT(12)
	#define MESON_SDHC_ICTL_TXFIFO_EMPTY			BIT(13)
	#define MESON_SDHC_ICTL_ADDI_DAT1_IRQ			BIT(14)
	#define MESON_SDHC_ICTL_ALL_IRQS			GENMASK(14, 0)
	#define MESON_SDHC_ICTL_DAT1_IRQ_DELAY			GENMASK(17, 16)

#define MESON_SDHC_ISTA						0x28
	#define MESON_SDHC_ISTA_RESP_OK				BIT(0)
	#define MESON_SDHC_ISTA_RESP_TIMEOUT			BIT(1)
	#define MESON_SDHC_ISTA_RESP_ERR_CRC			BIT(2)
	#define MESON_SDHC_ISTA_RESP_OK_NOCLEAR			BIT(3)
	#define MESON_SDHC_ISTA_DATA_1PACK_OK			BIT(4)
	#define MESON_SDHC_ISTA_DATA_TIMEOUT			BIT(5)
	#define MESON_SDHC_ISTA_DATA_ERR_CRC			BIT(6)
	#define MESON_SDHC_ISTA_DATA_XFER_OK			BIT(7)
	#define MESON_SDHC_ISTA_RX_HIGHER			BIT(8)
	#define MESON_SDHC_ISTA_RX_LOWER			BIT(9)
	#define MESON_SDHC_ISTA_DAT1_IRQ			BIT(10)
	#define MESON_SDHC_ISTA_DMA_DONE			BIT(11)
	#define MESON_SDHC_ISTA_RXFIFO_FULL			BIT(12)
	#define MESON_SDHC_ISTA_TXFIFO_EMPTY			BIT(13)
	#define MESON_SDHC_ISTA_ADDI_DAT1_IRQ			BIT(14)
	#define MESON_SDHC_ISTA_ALL_IRQS			GENMASK(14, 0)

#define MESON_SDHC_SRST						0x2c
	#define MESON_SDHC_SRST_MAIN_CTRL			BIT(0)
	#define MESON_SDHC_SRST_RXFIFO				BIT(1)
	#define MESON_SDHC_SRST_TXFIFO				BIT(2)
	#define MESON_SDHC_SRST_DPHY_RX				BIT(3)
	#define MESON_SDHC_SRST_DPHY_TX				BIT(4)
	#define MESON_SDHC_SRST_DMA_IF				BIT(5)

#define MESON_SDHC_ESTA						0x30
	#define MESON_SDHC_ESTA_11_13				GENMASK(13, 11)

#define MESON_SDHC_ENHC						0x34
	#define MESON_SDHC_ENHC_MESON8M2_WRRSP_MODE		BIT(0)
	#define MESON_SDHC_ENHC_MESON8M2_CHK_WRRSP		BIT(1)
	#define MESON_SDHC_ENHC_MESON8M2_CHK_DMA		BIT(2)
	#define MESON_SDHC_ENHC_MESON8M2_DEBUG			GENMASK(5, 3)
	#define MESON_SDHC_ENHC_MESON6_RX_TIMEOUT		GENMASK(7, 0)
	#define MESON_SDHC_ENHC_MESON6_DMA_RD_RESP		BIT(16)
	#define MESON_SDHC_ENHC_MESON6_DMA_WR_RESP		BIT(17)
	#define MESON_SDHC_ENHC_SDIO_IRQ_PERIOD			GENMASK(15, 8)
	#define MESON_SDHC_ENHC_RXFIFO_TH			GENMASK(24, 18)
	#define MESON_SDHC_ENHC_TXFIFO_TH			GENMASK(31, 25)

#define MESON_SDHC_CLK2						0x38
	#define MESON_SDHC_CLK2_RX_CLK_PHASE			GENMASK(11, 0)
	#define MESON_SDHC_CLK2_SD_CLK_PHASE			GENMASK(23, 12)

struct clk_bulk_data;

int meson_mx_sdhc_register_clkc(struct device *dev, void __iomem *base,
				struct clk_bulk_data *clk_bulk_data);

#endif /* _MESON_MX_SDHC_H_ */