linux-m68k.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v5 1/3] m68k: mcf5441x: add support for esdhc mmc controller
@ 2020-05-15 22:27 Angelo Dureghello
  2020-05-15 22:27 ` [PATCH v5 2/3] mmc: sdhci: add quirks for be to le byte swapping Angelo Dureghello
  2020-05-15 22:27 ` [PATCH v5 3/3] mmc: host: add Coldfire esdhc support Angelo Dureghello
  0 siblings, 2 replies; 7+ messages in thread
From: Angelo Dureghello @ 2020-05-15 22:27 UTC (permalink / raw)
  To: adrian.hunter, ulf.hansson, gerg; +Cc: linux-mmc, linux-m68k, Angelo Dureghello

Add support for sdhci-edshc mmc controller.

Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com>
---
Changes for v3:
- removed volatile cast from clk.c
Changes for v4:
- comment style fix in m5441xsim.h
Changes for v5:
- fix compilation warnings from __clk_enable2 and __clk_disable2
---
 arch/m68k/coldfire/clk.c                    | 15 ++++++++++
 arch/m68k/coldfire/device.c                 | 33 +++++++++++++++++++--
 arch/m68k/coldfire/m5441x.c                 | 12 +++++++-
 arch/m68k/include/asm/m5441xsim.h           | 15 ++++++++++
 arch/m68k/include/asm/mcfclk.h              |  2 ++
 include/linux/platform_data/mmc-esdhc-mcf.h | 17 +++++++++++
 6 files changed, 91 insertions(+), 3 deletions(-)
 create mode 100644 include/linux/platform_data/mmc-esdhc-mcf.h

diff --git a/arch/m68k/coldfire/clk.c b/arch/m68k/coldfire/clk.c
index 7bc666e482eb..75a057445472 100644
--- a/arch/m68k/coldfire/clk.c
+++ b/arch/m68k/coldfire/clk.c
@@ -73,6 +73,21 @@ struct clk_ops clk_ops1 = {
 #endif /* MCFPM_PPMCR1 */
 #endif /* MCFPM_PPMCR0 */
 
+static void __clk_enable2(struct clk *clk)
+{
+	__raw_writel(__raw_readl(MCFSDHC_CLK) | (1 << clk->slot), MCFSDHC_CLK);
+}
+
+static void __clk_disable2(struct clk *clk)
+{
+	__raw_writel(__raw_readl(MCFSDHC_CLK) & ~(1 << clk->slot), MCFSDHC_CLK);
+}
+
+struct clk_ops clk_ops2 = {
+	.enable		= __clk_enable2,
+	.disable	= __clk_disable2,
+};
+
 struct clk *clk_get(struct device *dev, const char *id)
 {
 	const char *clk_name = dev ? dev_name(dev) : id ? id : NULL;
diff --git a/arch/m68k/coldfire/device.c b/arch/m68k/coldfire/device.c
index b4103b6bfdeb..9ef4ec0aea00 100644
--- a/arch/m68k/coldfire/device.c
+++ b/arch/m68k/coldfire/device.c
@@ -22,6 +22,7 @@
 #include <asm/mcfqspi.h>
 #include <linux/platform_data/edma.h>
 #include <linux/platform_data/dma-mcf-edma.h>
+#include <linux/platform_data/mmc-esdhc-mcf.h>
 
 /*
  *	All current ColdFire parts contain from 2, 3, 4 or 10 UARTS.
@@ -551,9 +552,35 @@ static struct platform_device mcf_edma = {
 		.platform_data = &mcf_edma_data,
 	}
 };
-
 #endif /* IS_ENABLED(CONFIG_MCF_EDMA) */
 
+#if IS_ENABLED(CONFIG_MMC)
+static struct mcf_esdhc_platform_data mcf_esdhc_data = {
+	.max_bus_width = 4,
+	.cd_type = ESDHC_CD_NONE,
+};
+
+static struct resource mcf_esdhc_resources[] = {
+	{
+		.start = MCFSDHC_BASE,
+		.end = MCFSDHC_BASE + MCFSDHC_SIZE - 1,
+		.flags = IORESOURCE_MEM,
+	}, {
+		.start = MCF_IRQ_SDHC,
+		.end = MCF_IRQ_SDHC,
+		.flags = IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device mcf_esdhc = {
+	.name			= "sdhci-esdhc-mcf",
+	.id			= 0,
+	.num_resources		= ARRAY_SIZE(mcf_esdhc_resources),
+	.resource		= mcf_esdhc_resources,
+	.dev.platform_data	= &mcf_esdhc_data,
+};
+#endif /* IS_ENABLED(CONFIG_MMC) */
+
 static struct platform_device *mcf_devices[] __initdata = {
 	&mcf_uart,
 #if IS_ENABLED(CONFIG_FEC)
@@ -586,6 +613,9 @@ static struct platform_device *mcf_devices[] __initdata = {
 #if IS_ENABLED(CONFIG_MCF_EDMA)
 	&mcf_edma,
 #endif
+#if IS_ENABLED(CONFIG_MMC)
+	&mcf_esdhc,
+#endif
 };
 
 /*
@@ -614,4 +644,3 @@ static int __init mcf_init_devices(void)
 }
 
 arch_initcall(mcf_init_devices);
-
diff --git a/arch/m68k/coldfire/m5441x.c b/arch/m68k/coldfire/m5441x.c
index 5bd24c9b865d..ffa02de1a3fb 100644
--- a/arch/m68k/coldfire/m5441x.c
+++ b/arch/m68k/coldfire/m5441x.c
@@ -52,7 +52,7 @@ DEFINE_CLK(0, "mcfssi.0", 47, MCF_CLK);
 DEFINE_CLK(0, "pll.0", 48, MCF_CLK);
 DEFINE_CLK(0, "mcfrng.0", 49, MCF_CLK);
 DEFINE_CLK(0, "mcfssi.1", 50, MCF_CLK);
-DEFINE_CLK(0, "mcfsdhc.0", 51, MCF_CLK);
+DEFINE_CLK(0, "sdhci-esdhc-mcf.0", 51, MCF_CLK);
 DEFINE_CLK(0, "enet-fec.0", 53, MCF_CLK);
 DEFINE_CLK(0, "enet-fec.1", 54, MCF_CLK);
 DEFINE_CLK(0, "switch.0", 55, MCF_CLK);
@@ -74,6 +74,10 @@ DEFINE_CLK(1, "mcfpwm.0", 34, MCF_BUSCLK);
 DEFINE_CLK(1, "sys.0", 36, MCF_BUSCLK);
 DEFINE_CLK(1, "gpio.0", 37, MCF_BUSCLK);
 
+DEFINE_CLK(2, "ipg.0", 0, MCF_CLK);
+DEFINE_CLK(2, "ahb.0", 1, MCF_CLK);
+DEFINE_CLK(2, "per.0", 2, MCF_CLK);
+
 struct clk *mcf_clks[] = {
 	&__clk_0_2,
 	&__clk_0_8,
@@ -131,6 +135,11 @@ struct clk *mcf_clks[] = {
 	&__clk_1_34,
 	&__clk_1_36,
 	&__clk_1_37,
+
+	&__clk_2_0,
+	&__clk_2_1,
+	&__clk_2_2,
+
 	NULL,
 };
 
@@ -151,6 +160,7 @@ static struct clk * const enable_clks[] __initconst = {
 	&__clk_0_33, /* pit.1 */
 	&__clk_0_37, /* eport */
 	&__clk_0_48, /* pll */
+	&__clk_0_51, /* esdhc */
 
 	&__clk_1_36, /* CCM/reset module/Power management */
 	&__clk_1_37, /* gpio */
diff --git a/arch/m68k/include/asm/m5441xsim.h b/arch/m68k/include/asm/m5441xsim.h
index 4892f314ff38..e091e36d3464 100644
--- a/arch/m68k/include/asm/m5441xsim.h
+++ b/arch/m68k/include/asm/m5441xsim.h
@@ -278,6 +278,13 @@
 #define MCFGPIO_IRQ_VECBASE	(MCFINT_VECBASE - MCFGPIO_IRQ_MIN)
 #define MCFGPIO_PIN_MAX		87
 
+/*
+ * Phase Locked Loop (PLL)
+ */
+#define MCF_PLL_CR		0xFC0C0000
+#define MCF_PLL_DR		0xFC0C0004
+#define MCF_PLL_SR		0xFC0C0008
+
 /*
  *  DSPI module.
  */
@@ -298,5 +305,13 @@
 #define MCFEDMA_IRQ_INTR16	(MCFINT1_VECBASE + MCFEDMA_EDMA_INTR16)
 #define MCFEDMA_IRQ_INTR56	(MCFINT2_VECBASE + MCFEDMA_EDMA_INTR56)
 #define MCFEDMA_IRQ_ERR	(MCFINT0_VECBASE + MCFINT0_EDMA_ERR)
+/*
+ *  esdhc module.
+ */
+#define MCFSDHC_BASE		0xfc0cc000
+#define MCFSDHC_SIZE		256
+#define MCFINT2_SDHC		31
+#define MCF_IRQ_SDHC		(MCFINT2_VECBASE + MCFINT2_SDHC)
+#define MCFSDHC_CLK		(MCFSDHC_BASE + 0x2c)
 
 #endif /* m5441xsim_h */
diff --git a/arch/m68k/include/asm/mcfclk.h b/arch/m68k/include/asm/mcfclk.h
index 0aca504fae31..722627e06d66 100644
--- a/arch/m68k/include/asm/mcfclk.h
+++ b/arch/m68k/include/asm/mcfclk.h
@@ -30,6 +30,8 @@ extern struct clk_ops clk_ops0;
 extern struct clk_ops clk_ops1;
 #endif /* MCFPM_PPMCR1 */
 
+extern struct clk_ops clk_ops2;
+
 #define DEFINE_CLK(clk_bank, clk_name, clk_slot, clk_rate) \
 static struct clk __clk_##clk_bank##_##clk_slot = { \
 	.name = clk_name, \
diff --git a/include/linux/platform_data/mmc-esdhc-mcf.h b/include/linux/platform_data/mmc-esdhc-mcf.h
new file mode 100644
index 000000000000..85cb786a62fe
--- /dev/null
+++ b/include/linux/platform_data/mmc-esdhc-mcf.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __LINUX_PLATFORM_DATA_MCF_ESDHC_H__
+#define __LINUX_PLATFORM_DATA_MCF_ESDHC_H__
+
+enum cd_types {
+	ESDHC_CD_NONE,		/* no CD, neither controller nor gpio */
+	ESDHC_CD_CONTROLLER,	/* mmc controller internal CD */
+	ESDHC_CD_PERMANENT,	/* no CD, card permanently wired to host */
+};
+
+struct mcf_esdhc_platform_data {
+	int max_bus_width;
+	int cd_type;
+};
+
+#endif /* __LINUX_PLATFORM_DATA_MCF_ESDHC_H__ */
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 2/3] mmc: sdhci: add quirks for be to le byte swapping
  2020-05-15 22:27 [PATCH v5 1/3] m68k: mcf5441x: add support for esdhc mmc controller Angelo Dureghello
@ 2020-05-15 22:27 ` Angelo Dureghello
  2020-05-16 18:06   ` Adrian Hunter
  2020-05-15 22:27 ` [PATCH v5 3/3] mmc: host: add Coldfire esdhc support Angelo Dureghello
  1 sibling, 1 reply; 7+ messages in thread
From: Angelo Dureghello @ 2020-05-15 22:27 UTC (permalink / raw)
  To: adrian.hunter, ulf.hansson, gerg; +Cc: linux-mmc, linux-m68k, Angelo Dureghello

Some controller as the ColdFire eshdc may require an endianness
byte swap, becouse DMA read endianness is not configurable.

Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com>
---
Changes for v3:
- add post request callback
Changes for v4:
none
Changes for v5:
- remove useless comment before swapping bounce buffer
- rename pre_dma_transfer to copy_to_bounce_buffer
---
 drivers/mmc/host/sdhci.c | 9 ++++++---
 drivers/mmc/host/sdhci.h | 2 ++
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 3f716466fcfd..b7ba43e3b7c5 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -634,9 +634,12 @@ static int sdhci_pre_dma_transfer(struct sdhci_host *host,
 		}
 		if (mmc_get_dma_dir(data) == DMA_TO_DEVICE) {
 			/* Copy the data to the bounce buffer */
-			sg_copy_to_buffer(data->sg, data->sg_len,
-					  host->bounce_buffer,
-					  length);
+			if (host->ops->copy_to_bounce_buffer) {
+				host->ops->copy_to_bounce_buffer(host, data);
+			} else {
+				sg_copy_to_buffer(data->sg, data->sg_len,
+						  host->bounce_buffer, length);
+			}
 		}
 		/* Switch ownership to the DMA */
 		dma_sync_single_for_device(host->mmc->parent,
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 79dffbb731d3..632ea297bb60 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -653,6 +653,8 @@ struct sdhci_ops {
 	void	(*voltage_switch)(struct sdhci_host *host);
 	void	(*adma_write_desc)(struct sdhci_host *host, void **desc,
 				   dma_addr_t addr, int len, unsigned int cmd);
+	void	(*copy_to_bounce_buffer)(struct sdhci_host *host,
+					 struct mmc_data *data);
 	void	(*request_done)(struct sdhci_host *host,
 				struct mmc_request *mrq);
 };
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 3/3] mmc: host: add Coldfire esdhc support
  2020-05-15 22:27 [PATCH v5 1/3] m68k: mcf5441x: add support for esdhc mmc controller Angelo Dureghello
  2020-05-15 22:27 ` [PATCH v5 2/3] mmc: sdhci: add quirks for be to le byte swapping Angelo Dureghello
@ 2020-05-15 22:27 ` Angelo Dureghello
  2020-05-16 18:09   ` Adrian Hunter
  1 sibling, 1 reply; 7+ messages in thread
From: Angelo Dureghello @ 2020-05-15 22:27 UTC (permalink / raw)
  To: adrian.hunter, ulf.hansson, gerg; +Cc: linux-mmc, linux-m68k, Angelo Dureghello

This driver has been developed as a separate module starting
from the similar sdhci-esdhc-imx.c.

Reasons for a separate sdchi-esdhc-mcf driver:

- m68K architecture does not support devicetrees, so modifying
sdhci-of-esdhc.c that is devicetree-related adding platform data
seems not appropriate,
- clock-related part, has to be implemented specifically for
mcf5441x family (see esdhc_mcf_pltfm_set_clock()),
- this is a big endian cpu accessing a big endian controller,
but about sdma, this controller does not support hw swap, which
needs to be handled with specific code,
- some other minor differences but mainly to avoid risks on
tweaking inside largely used imx driver. Adding just a small
size ColdFire-specific driver, with benefits in a further less
risky maintenance.

Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com>
---
Changes for v3:
- fix write support
Changes for v4:
none
Changes for v5:
- better probe cleanup on errors
- test for bounce buffer to be allocated
- rename pre_dma_transfer to copy_to_bounce_buffer
- change swap to swap and copy to bounce buffer
- fix line alignments to pass checkpatch --strict
---
 drivers/mmc/host/Kconfig           |  13 +
 drivers/mmc/host/Makefile          |   1 +
 drivers/mmc/host/sdhci-esdhc-mcf.c | 522 +++++++++++++++++++++++++++++
 3 files changed, 536 insertions(+)
 create mode 100644 drivers/mmc/host/sdhci-esdhc-mcf.c

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 462b5352fea7..da793fc95203 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -235,6 +235,19 @@ config MMC_SDHCI_CNS3XXX
 
 	  If unsure, say N.
 
+config MMC_SDHCI_ESDHC_MCF
+	tristate "SDHCI support for the Freescale eSDHC ColdFire controller"
+	depends on M5441x
+	depends on MMC_SDHCI_PLTFM
+	select MMC_SDHCI_IO_ACCESSORS
+	help
+	  This selects the Freescale eSDHC controller support for
+	  ColdFire mcf5441x devices.
+
+	  If you have a controller with this interface, say Y or M here.
+
+	  If unsure, say N.
+
 config MMC_SDHCI_ESDHC_IMX
 	tristate "SDHCI support for the Freescale eSDHC/uSDHC i.MX controller"
 	depends on ARCH_MXC
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index b929ef941208..af2cdaadc4d3 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_MMC_REALTEK_USB)	+= rtsx_usb_sdmmc.o
 obj-$(CONFIG_MMC_SDHCI_PLTFM)		+= sdhci-pltfm.o
 obj-$(CONFIG_MMC_SDHCI_CADENCE)		+= sdhci-cadence.o
 obj-$(CONFIG_MMC_SDHCI_CNS3XXX)		+= sdhci-cns3xxx.o
+obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)       += sdhci-esdhc-mcf.o
 obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX)	+= sdhci-esdhc-imx.o
 obj-$(CONFIG_MMC_SDHCI_DOVE)		+= sdhci-dove.o
 obj-$(CONFIG_MMC_SDHCI_TEGRA)		+= sdhci-tegra.o
diff --git a/drivers/mmc/host/sdhci-esdhc-mcf.c b/drivers/mmc/host/sdhci-esdhc-mcf.c
new file mode 100644
index 000000000000..445faf76e676
--- /dev/null
+++ b/drivers/mmc/host/sdhci-esdhc-mcf.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Freescale eSDHC ColdFire family controller driver, platform bus.
+ *
+ * Copyright (c) 2020 Timesys Corporation
+ *   Author: Angelo Dureghello <angelo.dureghello@timesys.it>
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/platform_data/mmc-esdhc-mcf.h>
+#include <linux/mmc/mmc.h>
+#include "sdhci-pltfm.h"
+#include "sdhci-esdhc.h"
+
+#define	ESDHC_PROCTL_D3CD		0x08
+#define ESDHC_SYS_CTRL_DTOCV_MASK	0x0f
+#define ESDHC_DEFAULT_HOST_CONTROL	0x28
+
+/*
+ * Freescale eSDHC has DMA ERR flag at bit 28, not as std spec says, bit 25.
+ */
+#define ESDHC_INT_VENDOR_SPEC_DMA_ERR	BIT(28)
+
+struct pltfm_mcf_data {
+	struct clk *clk_ipg;
+	struct clk *clk_ahb;
+	struct clk *clk_per;
+	int aside;
+	int current_bus_width;
+};
+
+static inline void esdhc_mcf_buffer_swap32(u32 *buf, int len)
+{
+	int i;
+	u32 temp;
+
+	len = (len + 3) >> 2;
+
+	for (i = 0; i < len;  i++) {
+		temp = swab32(*buf);
+		*buf++ = temp;
+	}
+}
+
+static inline void esdhc_clrset_be(struct sdhci_host *host,
+				   u32 mask, u32 val, int reg)
+{
+	void __iomem *base = host->ioaddr + (reg & ~3);
+	u8 shift = (reg & 3) << 3;
+
+	mask <<= shift;
+	val <<= shift;
+
+	if (reg == SDHCI_HOST_CONTROL)
+		val |= ESDHC_PROCTL_D3CD;
+
+	writel((readl(base) & ~mask) | val, base);
+}
+
+/*
+ * Note: mcf is big-endian, single bytes need to be accessed at big endian
+ * offsets.
+ */
+static void esdhc_mcf_writeb_be(struct sdhci_host *host, u8 val, int reg)
+{
+	void __iomem *base = host->ioaddr + (reg & ~3);
+	u8 shift = (reg & 3) << 3;
+	u32 mask = ~(0xff << shift);
+
+	if (reg == SDHCI_HOST_CONTROL) {
+		u32 host_ctrl = ESDHC_DEFAULT_HOST_CONTROL;
+		u8 dma_bits = (val & SDHCI_CTRL_DMA_MASK) >> 3;
+		u8 tmp = readb(host->ioaddr + SDHCI_HOST_CONTROL + 1);
+
+		tmp &= ~0x03;
+		tmp |= dma_bits;
+
+		/*
+		 * Recomposition needed, restore always endianness and
+		 * keep D3CD and AI, just setting bus width.
+		 */
+		host_ctrl |= val;
+		host_ctrl |= (dma_bits << 8);
+		writel(host_ctrl, host->ioaddr + SDHCI_HOST_CONTROL);
+
+		return;
+	}
+
+	writel((readl(base) & mask) | (val << shift), base);
+}
+
+static void esdhc_mcf_writew_be(struct sdhci_host *host, u16 val, int reg)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
+	void __iomem *base = host->ioaddr + (reg & ~3);
+	u8 shift = (reg & 3) << 3;
+	u32 mask = ~(0xffff << shift);
+
+	switch (reg) {
+	case SDHCI_TRANSFER_MODE:
+		mcf_data->aside = val;
+		return;
+	case SDHCI_COMMAND:
+		if (host->cmd->opcode == MMC_STOP_TRANSMISSION)
+			val |= SDHCI_CMD_ABORTCMD;
+
+		/*
+		 * As for the fsl driver,
+		 * we have to set the mode in a single write here.
+		 */
+		writel(val << 16 | mcf_data->aside,
+		       host->ioaddr + SDHCI_TRANSFER_MODE);
+		return;
+	}
+
+	writel((readl(base) & mask) | (val << shift), base);
+}
+
+static void esdhc_mcf_writel_be(struct sdhci_host *host, u32 val, int reg)
+{
+	writel(val, host->ioaddr + reg);
+}
+
+static u8 esdhc_mcf_readb_be(struct sdhci_host *host, int reg)
+{
+	if (reg == SDHCI_HOST_CONTROL) {
+		u8 __iomem *base = host->ioaddr + (reg & ~3);
+		u16 val = readw(base + 2);
+		u8 dma_bits = (val >> 5) & SDHCI_CTRL_DMA_MASK;
+		u8 host_ctrl = val & 0xff;
+
+		host_ctrl &= ~SDHCI_CTRL_DMA_MASK;
+		host_ctrl |= dma_bits;
+
+		return host_ctrl;
+	}
+
+	return readb(host->ioaddr + (reg ^ 0x3));
+}
+
+static u16 esdhc_mcf_readw_be(struct sdhci_host *host, int reg)
+{
+	/*
+	 * For SDHCI_HOST_VERSION, sdhci specs defines 0xFE,
+	 * a wrong offset for us, we are at 0xFC.
+	 */
+	if (reg == SDHCI_HOST_VERSION)
+		reg -= 2;
+
+	return readw(host->ioaddr + (reg ^ 0x2));
+}
+
+static u32 esdhc_mcf_readl_be(struct sdhci_host *host, int reg)
+{
+	u32 val;
+
+	val = readl(host->ioaddr + reg);
+
+	/*
+	 * RM (25.3.9) sd pin clock must never exceed 25Mhz.
+	 * So forcing legacy mode at 25Mhz.
+	 */
+	if (unlikely(reg == SDHCI_CAPABILITIES))
+		val &= ~SDHCI_CAN_DO_HISPD;
+
+	if (unlikely(reg == SDHCI_INT_STATUS)) {
+		if (val & ESDHC_INT_VENDOR_SPEC_DMA_ERR) {
+			val &= ~ESDHC_INT_VENDOR_SPEC_DMA_ERR;
+			val |= SDHCI_INT_ADMA_ERROR;
+		}
+	}
+
+	return val;
+}
+
+static unsigned int esdhc_mcf_get_max_timeout_count(struct sdhci_host *host)
+{
+	return 1 << 27;
+}
+
+static void esdhc_mcf_set_timeout(struct sdhci_host *host,
+				  struct mmc_command *cmd)
+{
+	/* Use maximum timeout counter */
+	esdhc_clrset_be(host, ESDHC_SYS_CTRL_DTOCV_MASK, 0xE,
+			SDHCI_TIMEOUT_CONTROL);
+}
+
+static void esdhc_mcf_reset(struct sdhci_host *host, u8 mask)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
+
+	sdhci_reset(host, mask);
+
+	esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
+			mcf_data->current_bus_width, SDHCI_HOST_CONTROL);
+
+	sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+	sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+}
+
+static unsigned int esdhc_mcf_pltfm_get_max_clock(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+	return pltfm_host->clock;
+}
+
+static unsigned int esdhc_mcf_pltfm_get_min_clock(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+	return pltfm_host->clock / 256 / 16;
+}
+
+static void esdhc_mcf_pltfm_set_clock(struct sdhci_host *host,
+				      unsigned int clock)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	unsigned long *pll_dr = (unsigned long *)MCF_PLL_DR;
+	u32 fvco, fsys, fesdhc, temp;
+	const int sdclkfs[] = {2, 4, 8, 16, 32, 64, 128, 256};
+	int delta, old_delta = clock;
+	int i, q, ri, rq;
+
+	if (clock == 0) {
+		host->mmc->actual_clock = 0;
+		return;
+	}
+
+	/*
+	 * ColdFire eSDHC clock.s
+	 *
+	 * pll -+-> / outdiv1 --> fsys
+	 *      +-> / outdiv3 --> eSDHC clock ---> / SDCCLKFS / DVS
+	 *
+	 * mcf5441x datasheet says:
+	 * (8.1.2) eSDHC should be 40 MHz max
+	 * (25.3.9) eSDHC input is, as example, 96 Mhz ...
+	 * (25.3.9) sd pin clock must never exceed 25Mhz
+	 *
+	 * fvco = fsys * outdvi1 + 1
+	 * fshdc = fvco / outdiv3 + 1
+	 */
+	temp = readl(pll_dr);
+	fsys = pltfm_host->clock;
+	fvco = fsys * ((temp & 0x1f) + 1);
+	fesdhc = fvco / (((temp >> 10) & 0x1f) + 1);
+
+	for (i = 0; i < 8; ++i) {
+		int result = fesdhc / sdclkfs[i];
+
+		for (q = 1; q < 17; ++q) {
+			int finale = result / q;
+
+			delta = abs(clock - finale);
+
+			if (delta < old_delta) {
+				old_delta = delta;
+				ri = i;
+				rq = q;
+			}
+		}
+	}
+
+	/*
+	 * Apply divisors and re-enable all the clocks
+	 */
+	temp = ((sdclkfs[ri] >> 1) << 8) | ((rq - 1) << 4) |
+		(ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN);
+	esdhc_clrset_be(host, 0x0000fff7, temp, SDHCI_CLOCK_CONTROL);
+
+	host->mmc->actual_clock = clock;
+
+	mdelay(1);
+}
+
+static void esdhc_mcf_pltfm_set_bus_width(struct sdhci_host *host, int width)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
+
+	switch (width) {
+	case MMC_BUS_WIDTH_4:
+		mcf_data->current_bus_width = ESDHC_CTRL_4BITBUS;
+		break;
+	default:
+		mcf_data->current_bus_width = 0;
+		break;
+	}
+
+	esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
+			mcf_data->current_bus_width, SDHCI_HOST_CONTROL);
+}
+
+static void esdhc_mcf_request_done(struct sdhci_host *host,
+				   struct mmc_request *mrq)
+{
+	struct scatterlist *sg;
+	u32 *buffer;
+	int i;
+
+	if (!mrq->data || !mrq->data->bytes_xfered)
+		goto exit_done;
+
+	if (mmc_get_dma_dir(mrq->data) != DMA_FROM_DEVICE)
+		goto exit_done;
+
+	/*
+	 * On mcf5441x there is no hw sdma option/flag to select the dma
+	 * transfer endiannes. A swap after the transfer is needed.
+	 */
+	for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i) {
+		buffer = (u32 *)sg_virt(sg);
+		esdhc_mcf_buffer_swap32(buffer, sg->length);
+	}
+
+exit_done:
+	mmc_request_done(host->mmc, mrq);
+}
+
+static void esdhc_mcf_copy_to_bounce_buffer(struct sdhci_host *host,
+					    struct mmc_data *data)
+{
+	unsigned int length = data->blksz * data->blocks;
+
+	sg_copy_to_buffer(data->sg, data->sg_len,
+			  host->bounce_buffer, length);
+
+	esdhc_mcf_buffer_swap32((u32 *)host->bounce_buffer,
+				data->blksz * data->blocks);
+}
+
+static struct sdhci_ops sdhci_esdhc_ops = {
+	.reset = esdhc_mcf_reset,
+	.set_clock = esdhc_mcf_pltfm_set_clock,
+	.get_max_clock = esdhc_mcf_pltfm_get_max_clock,
+	.get_min_clock = esdhc_mcf_pltfm_get_min_clock,
+	.set_bus_width = esdhc_mcf_pltfm_set_bus_width,
+	.get_max_timeout_count = esdhc_mcf_get_max_timeout_count,
+	.set_timeout = esdhc_mcf_set_timeout,
+	.write_b = esdhc_mcf_writeb_be,
+	.write_w = esdhc_mcf_writew_be,
+	.write_l = esdhc_mcf_writel_be,
+	.read_b = esdhc_mcf_readb_be,
+	.read_w = esdhc_mcf_readw_be,
+	.read_l = esdhc_mcf_readl_be,
+	.copy_to_bounce_buffer = esdhc_mcf_copy_to_bounce_buffer,
+	.request_done = esdhc_mcf_request_done,
+};
+
+static const struct sdhci_pltfm_data sdhci_esdhc_mcf_pdata = {
+	.ops = &sdhci_esdhc_ops,
+	.quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_FORCE_DMA,
+		 /*
+		  * Mandatory quirk,
+		  * controller does not support cmd23,
+		  * without, on > 8G cards cmd23 is used, and
+		  * driver times out.
+		  */
+		  SDHCI_QUIRK2_HOST_NO_CMD23,
+};
+
+static int esdhc_mcf_plat_init(struct sdhci_host *host,
+			       struct pltfm_mcf_data *mcf_data)
+{
+	struct mcf_esdhc_platform_data *plat_data;
+
+	if (!host->mmc->parent->platform_data) {
+		dev_err(mmc_dev(host->mmc), "no platform data!\n");
+		return -EINVAL;
+	}
+
+	plat_data = (struct mcf_esdhc_platform_data *)
+			host->mmc->parent->platform_data;
+
+	/* Card_detect */
+	switch (plat_data->cd_type) {
+	default:
+	case ESDHC_CD_CONTROLLER:
+		/* We have a working card_detect back */
+		host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+		break;
+	case ESDHC_CD_PERMANENT:
+		host->mmc->caps |= MMC_CAP_NONREMOVABLE;
+		break;
+	case ESDHC_CD_NONE:
+		break;
+	}
+
+	switch (plat_data->max_bus_width) {
+	case 4:
+		host->mmc->caps |= MMC_CAP_4_BIT_DATA;
+		break;
+	case 1:
+	default:
+		host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
+		break;
+	}
+
+	return 0;
+}
+
+static int sdhci_esdhc_mcf_probe(struct platform_device *pdev)
+{
+	struct sdhci_host *host;
+	struct sdhci_pltfm_host *pltfm_host;
+	struct pltfm_mcf_data *mcf_data;
+	int err;
+
+	host = sdhci_pltfm_init(pdev, &sdhci_esdhc_mcf_pdata,
+				sizeof(*mcf_data));
+
+	if (IS_ERR(host))
+		return PTR_ERR(host);
+
+	pltfm_host = sdhci_priv(host);
+	mcf_data = sdhci_pltfm_priv(pltfm_host);
+
+	host->sdma_boundary = 0;
+
+	host->flags |= SDHCI_AUTO_CMD12;
+
+	mcf_data->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
+	if (IS_ERR(mcf_data->clk_ipg)) {
+		err = PTR_ERR(mcf_data->clk_ipg);
+		goto err_exit;
+	}
+
+	mcf_data->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
+	if (IS_ERR(mcf_data->clk_ahb)) {
+		err = PTR_ERR(mcf_data->clk_ahb);
+		goto err_exit;
+	}
+
+	mcf_data->clk_per = devm_clk_get(&pdev->dev, "per");
+	if (IS_ERR(mcf_data->clk_per)) {
+		err = PTR_ERR(mcf_data->clk_per);
+		goto err_exit;
+	}
+
+	pltfm_host->clk = mcf_data->clk_per;
+	pltfm_host->clock = clk_get_rate(pltfm_host->clk);
+	err = clk_prepare_enable(mcf_data->clk_per);
+	if (err)
+		goto err_exit;
+
+	err = clk_prepare_enable(mcf_data->clk_ipg);
+	if (err)
+		goto unprep_per;
+
+	err = clk_prepare_enable(mcf_data->clk_ahb);
+	if (err)
+		goto unprep_ipg;
+
+	err = esdhc_mcf_plat_init(host, mcf_data);
+	if (err)
+		goto unprep_ahb;
+
+	err = sdhci_setup_host(host);
+	if (err)
+		goto unprep_ahb;
+
+	if (!host->bounce_buffer) {
+		dev_err(&pdev->dev, "bounce buffer not allocated");
+		err = -ENOMEM;
+		goto cleanup;
+	}
+
+	err = __sdhci_add_host(host);
+	if (err)
+		goto cleanup;
+
+	return 0;
+
+cleanup:
+	sdhci_cleanup_host(host);
+unprep_ahb:
+	clk_disable_unprepare(mcf_data->clk_ahb);
+unprep_ipg:
+	clk_disable_unprepare(mcf_data->clk_ipg);
+unprep_per:
+	clk_disable_unprepare(mcf_data->clk_per);
+err_exit:
+	sdhci_pltfm_free(pdev);
+
+	return err;
+}
+
+static int sdhci_esdhc_mcf_remove(struct platform_device *pdev)
+{
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
+
+	sdhci_remove_host(host, 0);
+
+	clk_disable_unprepare(mcf_data->clk_ipg);
+	clk_disable_unprepare(mcf_data->clk_ahb);
+	clk_disable_unprepare(mcf_data->clk_per);
+
+	sdhci_pltfm_free(pdev);
+
+	return 0;
+}
+
+static struct platform_driver sdhci_esdhc_mcf_driver = {
+	.driver	= {
+		.name = "sdhci-esdhc-mcf",
+	},
+	.probe = sdhci_esdhc_mcf_probe,
+	.remove = sdhci_esdhc_mcf_remove,
+};
+
+module_platform_driver(sdhci_esdhc_mcf_driver);
+
+MODULE_DESCRIPTION("SDHCI driver for Freescale ColdFire eSDHC");
+MODULE_AUTHOR("Angelo Dureghello <angelo.dureghello@timesys.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v5 2/3] mmc: sdhci: add quirks for be to le byte swapping
  2020-05-15 22:27 ` [PATCH v5 2/3] mmc: sdhci: add quirks for be to le byte swapping Angelo Dureghello
@ 2020-05-16 18:06   ` Adrian Hunter
  0 siblings, 0 replies; 7+ messages in thread
From: Adrian Hunter @ 2020-05-16 18:06 UTC (permalink / raw)
  To: Angelo Dureghello, ulf.hansson, gerg; +Cc: linux-mmc, linux-m68k

On 16/05/20 1:27 am, Angelo Dureghello wrote:
> Some controller as the ColdFire eshdc may require an endianness
> byte swap, becouse DMA read endianness is not configurable.

becouse -> because

Needs a bit more explanation i.e.

Facilitate using the bounce buffer for this by adding ->copy_to_bounce_buffer().

Nevertheless:

Acked-by: Adrian Hunter <adrian.hunter@intel.com>

> 
> Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com>
> ---
> Changes for v3:
> - add post request callback
> Changes for v4:
> none
> Changes for v5:
> - remove useless comment before swapping bounce buffer
> - rename pre_dma_transfer to copy_to_bounce_buffer
> ---
>  drivers/mmc/host/sdhci.c | 9 ++++++---
>  drivers/mmc/host/sdhci.h | 2 ++
>  2 files changed, 8 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 3f716466fcfd..b7ba43e3b7c5 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -634,9 +634,12 @@ static int sdhci_pre_dma_transfer(struct sdhci_host *host,
>  		}
>  		if (mmc_get_dma_dir(data) == DMA_TO_DEVICE) {
>  			/* Copy the data to the bounce buffer */
> -			sg_copy_to_buffer(data->sg, data->sg_len,
> -					  host->bounce_buffer,
> -					  length);
> +			if (host->ops->copy_to_bounce_buffer) {
> +				host->ops->copy_to_bounce_buffer(host, data);
> +			} else {
> +				sg_copy_to_buffer(data->sg, data->sg_len,
> +						  host->bounce_buffer, length);
> +			}
>  		}
>  		/* Switch ownership to the DMA */
>  		dma_sync_single_for_device(host->mmc->parent,
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index 79dffbb731d3..632ea297bb60 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -653,6 +653,8 @@ struct sdhci_ops {
>  	void	(*voltage_switch)(struct sdhci_host *host);
>  	void	(*adma_write_desc)(struct sdhci_host *host, void **desc,
>  				   dma_addr_t addr, int len, unsigned int cmd);
> +	void	(*copy_to_bounce_buffer)(struct sdhci_host *host,
> +					 struct mmc_data *data);
>  	void	(*request_done)(struct sdhci_host *host,
>  				struct mmc_request *mrq);
>  };
> 


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v5 3/3] mmc: host: add Coldfire esdhc support
  2020-05-15 22:27 ` [PATCH v5 3/3] mmc: host: add Coldfire esdhc support Angelo Dureghello
@ 2020-05-16 18:09   ` Adrian Hunter
  2020-05-18  8:00     ` Angelo Dureghello
  0 siblings, 1 reply; 7+ messages in thread
From: Adrian Hunter @ 2020-05-16 18:09 UTC (permalink / raw)
  To: Angelo Dureghello, ulf.hansson, gerg; +Cc: linux-mmc, linux-m68k

On 16/05/20 1:27 am, Angelo Dureghello wrote:
> This driver has been developed as a separate module starting
> from the similar sdhci-esdhc-imx.c.
> 
> Reasons for a separate sdchi-esdhc-mcf driver:
> 
> - m68K architecture does not support devicetrees, so modifying
> sdhci-of-esdhc.c that is devicetree-related adding platform data
> seems not appropriate,
> - clock-related part, has to be implemented specifically for
> mcf5441x family (see esdhc_mcf_pltfm_set_clock()),
> - this is a big endian cpu accessing a big endian controller,
> but about sdma, this controller does not support hw swap, which
> needs to be handled with specific code,
> - some other minor differences but mainly to avoid risks on
> tweaking inside largely used imx driver. Adding just a small
> size ColdFire-specific driver, with benefits in a further less
> risky maintenance.
> 
> Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com>

One minor comment below, nevertheless:

Acked-by: Adrian Hunter <adrian.hunter@intel.com>

> ---
> Changes for v3:
> - fix write support
> Changes for v4:
> none
> Changes for v5:
> - better probe cleanup on errors
> - test for bounce buffer to be allocated
> - rename pre_dma_transfer to copy_to_bounce_buffer
> - change swap to swap and copy to bounce buffer
> - fix line alignments to pass checkpatch --strict
> ---
>  drivers/mmc/host/Kconfig           |  13 +
>  drivers/mmc/host/Makefile          |   1 +
>  drivers/mmc/host/sdhci-esdhc-mcf.c | 522 +++++++++++++++++++++++++++++
>  3 files changed, 536 insertions(+)
>  create mode 100644 drivers/mmc/host/sdhci-esdhc-mcf.c
> 
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 462b5352fea7..da793fc95203 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -235,6 +235,19 @@ config MMC_SDHCI_CNS3XXX
>  
>  	  If unsure, say N.
>  
> +config MMC_SDHCI_ESDHC_MCF
> +	tristate "SDHCI support for the Freescale eSDHC ColdFire controller"
> +	depends on M5441x
> +	depends on MMC_SDHCI_PLTFM
> +	select MMC_SDHCI_IO_ACCESSORS
> +	help
> +	  This selects the Freescale eSDHC controller support for
> +	  ColdFire mcf5441x devices.
> +
> +	  If you have a controller with this interface, say Y or M here.
> +
> +	  If unsure, say N.
> +
>  config MMC_SDHCI_ESDHC_IMX
>  	tristate "SDHCI support for the Freescale eSDHC/uSDHC i.MX controller"
>  	depends on ARCH_MXC
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index b929ef941208..af2cdaadc4d3 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -82,6 +82,7 @@ obj-$(CONFIG_MMC_REALTEK_USB)	+= rtsx_usb_sdmmc.o
>  obj-$(CONFIG_MMC_SDHCI_PLTFM)		+= sdhci-pltfm.o
>  obj-$(CONFIG_MMC_SDHCI_CADENCE)		+= sdhci-cadence.o
>  obj-$(CONFIG_MMC_SDHCI_CNS3XXX)		+= sdhci-cns3xxx.o
> +obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)       += sdhci-esdhc-mcf.o
>  obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX)	+= sdhci-esdhc-imx.o
>  obj-$(CONFIG_MMC_SDHCI_DOVE)		+= sdhci-dove.o
>  obj-$(CONFIG_MMC_SDHCI_TEGRA)		+= sdhci-tegra.o
> diff --git a/drivers/mmc/host/sdhci-esdhc-mcf.c b/drivers/mmc/host/sdhci-esdhc-mcf.c
> new file mode 100644
> index 000000000000..445faf76e676
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-esdhc-mcf.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Freescale eSDHC ColdFire family controller driver, platform bus.
> + *
> + * Copyright (c) 2020 Timesys Corporation
> + *   Author: Angelo Dureghello <angelo.dureghello@timesys.it>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/delay.h>
> +#include <linux/platform_data/mmc-esdhc-mcf.h>
> +#include <linux/mmc/mmc.h>
> +#include "sdhci-pltfm.h"
> +#include "sdhci-esdhc.h"
> +
> +#define	ESDHC_PROCTL_D3CD		0x08
> +#define ESDHC_SYS_CTRL_DTOCV_MASK	0x0f
> +#define ESDHC_DEFAULT_HOST_CONTROL	0x28
> +
> +/*
> + * Freescale eSDHC has DMA ERR flag at bit 28, not as std spec says, bit 25.
> + */
> +#define ESDHC_INT_VENDOR_SPEC_DMA_ERR	BIT(28)
> +
> +struct pltfm_mcf_data {
> +	struct clk *clk_ipg;
> +	struct clk *clk_ahb;
> +	struct clk *clk_per;
> +	int aside;
> +	int current_bus_width;
> +};
> +
> +static inline void esdhc_mcf_buffer_swap32(u32 *buf, int len)
> +{
> +	int i;
> +	u32 temp;
> +
> +	len = (len + 3) >> 2;
> +
> +	for (i = 0; i < len;  i++) {
> +		temp = swab32(*buf);
> +		*buf++ = temp;
> +	}
> +}
> +
> +static inline void esdhc_clrset_be(struct sdhci_host *host,
> +				   u32 mask, u32 val, int reg)
> +{
> +	void __iomem *base = host->ioaddr + (reg & ~3);
> +	u8 shift = (reg & 3) << 3;
> +
> +	mask <<= shift;
> +	val <<= shift;
> +
> +	if (reg == SDHCI_HOST_CONTROL)
> +		val |= ESDHC_PROCTL_D3CD;
> +
> +	writel((readl(base) & ~mask) | val, base);
> +}
> +
> +/*
> + * Note: mcf is big-endian, single bytes need to be accessed at big endian
> + * offsets.
> + */
> +static void esdhc_mcf_writeb_be(struct sdhci_host *host, u8 val, int reg)
> +{
> +	void __iomem *base = host->ioaddr + (reg & ~3);
> +	u8 shift = (reg & 3) << 3;
> +	u32 mask = ~(0xff << shift);
> +
> +	if (reg == SDHCI_HOST_CONTROL) {
> +		u32 host_ctrl = ESDHC_DEFAULT_HOST_CONTROL;
> +		u8 dma_bits = (val & SDHCI_CTRL_DMA_MASK) >> 3;
> +		u8 tmp = readb(host->ioaddr + SDHCI_HOST_CONTROL + 1);
> +
> +		tmp &= ~0x03;
> +		tmp |= dma_bits;
> +
> +		/*
> +		 * Recomposition needed, restore always endianness and
> +		 * keep D3CD and AI, just setting bus width.
> +		 */
> +		host_ctrl |= val;
> +		host_ctrl |= (dma_bits << 8);
> +		writel(host_ctrl, host->ioaddr + SDHCI_HOST_CONTROL);
> +
> +		return;
> +	}
> +
> +	writel((readl(base) & mask) | (val << shift), base);
> +}
> +
> +static void esdhc_mcf_writew_be(struct sdhci_host *host, u16 val, int reg)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> +	void __iomem *base = host->ioaddr + (reg & ~3);
> +	u8 shift = (reg & 3) << 3;
> +	u32 mask = ~(0xffff << shift);
> +
> +	switch (reg) {
> +	case SDHCI_TRANSFER_MODE:
> +		mcf_data->aside = val;
> +		return;
> +	case SDHCI_COMMAND:
> +		if (host->cmd->opcode == MMC_STOP_TRANSMISSION)
> +			val |= SDHCI_CMD_ABORTCMD;
> +
> +		/*
> +		 * As for the fsl driver,
> +		 * we have to set the mode in a single write here.
> +		 */
> +		writel(val << 16 | mcf_data->aside,
> +		       host->ioaddr + SDHCI_TRANSFER_MODE);
> +		return;
> +	}
> +
> +	writel((readl(base) & mask) | (val << shift), base);
> +}
> +
> +static void esdhc_mcf_writel_be(struct sdhci_host *host, u32 val, int reg)
> +{
> +	writel(val, host->ioaddr + reg);
> +}
> +
> +static u8 esdhc_mcf_readb_be(struct sdhci_host *host, int reg)
> +{
> +	if (reg == SDHCI_HOST_CONTROL) {
> +		u8 __iomem *base = host->ioaddr + (reg & ~3);
> +		u16 val = readw(base + 2);
> +		u8 dma_bits = (val >> 5) & SDHCI_CTRL_DMA_MASK;
> +		u8 host_ctrl = val & 0xff;
> +
> +		host_ctrl &= ~SDHCI_CTRL_DMA_MASK;
> +		host_ctrl |= dma_bits;
> +
> +		return host_ctrl;
> +	}
> +
> +	return readb(host->ioaddr + (reg ^ 0x3));
> +}
> +
> +static u16 esdhc_mcf_readw_be(struct sdhci_host *host, int reg)
> +{
> +	/*
> +	 * For SDHCI_HOST_VERSION, sdhci specs defines 0xFE,
> +	 * a wrong offset for us, we are at 0xFC.
> +	 */
> +	if (reg == SDHCI_HOST_VERSION)
> +		reg -= 2;
> +
> +	return readw(host->ioaddr + (reg ^ 0x2));
> +}
> +
> +static u32 esdhc_mcf_readl_be(struct sdhci_host *host, int reg)
> +{
> +	u32 val;
> +
> +	val = readl(host->ioaddr + reg);
> +
> +	/*
> +	 * RM (25.3.9) sd pin clock must never exceed 25Mhz.
> +	 * So forcing legacy mode at 25Mhz.
> +	 */
> +	if (unlikely(reg == SDHCI_CAPABILITIES))
> +		val &= ~SDHCI_CAN_DO_HISPD;
> +
> +	if (unlikely(reg == SDHCI_INT_STATUS)) {
> +		if (val & ESDHC_INT_VENDOR_SPEC_DMA_ERR) {
> +			val &= ~ESDHC_INT_VENDOR_SPEC_DMA_ERR;
> +			val |= SDHCI_INT_ADMA_ERROR;
> +		}
> +	}
> +
> +	return val;
> +}
> +
> +static unsigned int esdhc_mcf_get_max_timeout_count(struct sdhci_host *host)
> +{
> +	return 1 << 27;
> +}
> +
> +static void esdhc_mcf_set_timeout(struct sdhci_host *host,
> +				  struct mmc_command *cmd)
> +{
> +	/* Use maximum timeout counter */
> +	esdhc_clrset_be(host, ESDHC_SYS_CTRL_DTOCV_MASK, 0xE,
> +			SDHCI_TIMEOUT_CONTROL);
> +}
> +
> +static void esdhc_mcf_reset(struct sdhci_host *host, u8 mask)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> +
> +	sdhci_reset(host, mask);
> +
> +	esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
> +			mcf_data->current_bus_width, SDHCI_HOST_CONTROL);
> +
> +	sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
> +	sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
> +}
> +
> +static unsigned int esdhc_mcf_pltfm_get_max_clock(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +
> +	return pltfm_host->clock;
> +}
> +
> +static unsigned int esdhc_mcf_pltfm_get_min_clock(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +
> +	return pltfm_host->clock / 256 / 16;
> +}
> +
> +static void esdhc_mcf_pltfm_set_clock(struct sdhci_host *host,
> +				      unsigned int clock)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	unsigned long *pll_dr = (unsigned long *)MCF_PLL_DR;
> +	u32 fvco, fsys, fesdhc, temp;
> +	const int sdclkfs[] = {2, 4, 8, 16, 32, 64, 128, 256};
> +	int delta, old_delta = clock;
> +	int i, q, ri, rq;
> +
> +	if (clock == 0) {
> +		host->mmc->actual_clock = 0;
> +		return;
> +	}
> +
> +	/*
> +	 * ColdFire eSDHC clock.s
> +	 *
> +	 * pll -+-> / outdiv1 --> fsys
> +	 *      +-> / outdiv3 --> eSDHC clock ---> / SDCCLKFS / DVS
> +	 *
> +	 * mcf5441x datasheet says:
> +	 * (8.1.2) eSDHC should be 40 MHz max
> +	 * (25.3.9) eSDHC input is, as example, 96 Mhz ...
> +	 * (25.3.9) sd pin clock must never exceed 25Mhz
> +	 *
> +	 * fvco = fsys * outdvi1 + 1
> +	 * fshdc = fvco / outdiv3 + 1
> +	 */
> +	temp = readl(pll_dr);
> +	fsys = pltfm_host->clock;
> +	fvco = fsys * ((temp & 0x1f) + 1);
> +	fesdhc = fvco / (((temp >> 10) & 0x1f) + 1);
> +
> +	for (i = 0; i < 8; ++i) {
> +		int result = fesdhc / sdclkfs[i];
> +
> +		for (q = 1; q < 17; ++q) {
> +			int finale = result / q;
> +
> +			delta = abs(clock - finale);
> +
> +			if (delta < old_delta) {
> +				old_delta = delta;
> +				ri = i;
> +				rq = q;
> +			}
> +		}
> +	}
> +
> +	/*
> +	 * Apply divisors and re-enable all the clocks
> +	 */
> +	temp = ((sdclkfs[ri] >> 1) << 8) | ((rq - 1) << 4) |
> +		(ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN);
> +	esdhc_clrset_be(host, 0x0000fff7, temp, SDHCI_CLOCK_CONTROL);
> +
> +	host->mmc->actual_clock = clock;
> +
> +	mdelay(1);
> +}
> +
> +static void esdhc_mcf_pltfm_set_bus_width(struct sdhci_host *host, int width)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> +
> +	switch (width) {
> +	case MMC_BUS_WIDTH_4:
> +		mcf_data->current_bus_width = ESDHC_CTRL_4BITBUS;
> +		break;
> +	default:
> +		mcf_data->current_bus_width = 0;
> +		break;
> +	}
> +
> +	esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
> +			mcf_data->current_bus_width, SDHCI_HOST_CONTROL);
> +}
> +
> +static void esdhc_mcf_request_done(struct sdhci_host *host,
> +				   struct mmc_request *mrq)
> +{
> +	struct scatterlist *sg;
> +	u32 *buffer;
> +	int i;
> +
> +	if (!mrq->data || !mrq->data->bytes_xfered)
> +		goto exit_done;
> +
> +	if (mmc_get_dma_dir(mrq->data) != DMA_FROM_DEVICE)
> +		goto exit_done;
> +
> +	/*
> +	 * On mcf5441x there is no hw sdma option/flag to select the dma
> +	 * transfer endiannes. A swap after the transfer is needed.
> +	 */
> +	for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i) {
> +		buffer = (u32 *)sg_virt(sg);
> +		esdhc_mcf_buffer_swap32(buffer, sg->length);
> +	}
> +
> +exit_done:
> +	mmc_request_done(host->mmc, mrq);
> +}
> +
> +static void esdhc_mcf_copy_to_bounce_buffer(struct sdhci_host *host,
> +					    struct mmc_data *data)
> +{
> +	unsigned int length = data->blksz * data->blocks;
> +
> +	sg_copy_to_buffer(data->sg, data->sg_len,
> +			  host->bounce_buffer, length);
> +
> +	esdhc_mcf_buffer_swap32((u32 *)host->bounce_buffer,
> +				data->blksz * data->blocks);

data->blksz * data->blocks is already calculated as 'length'

> +}
> +
> +static struct sdhci_ops sdhci_esdhc_ops = {
> +	.reset = esdhc_mcf_reset,
> +	.set_clock = esdhc_mcf_pltfm_set_clock,
> +	.get_max_clock = esdhc_mcf_pltfm_get_max_clock,
> +	.get_min_clock = esdhc_mcf_pltfm_get_min_clock,
> +	.set_bus_width = esdhc_mcf_pltfm_set_bus_width,
> +	.get_max_timeout_count = esdhc_mcf_get_max_timeout_count,
> +	.set_timeout = esdhc_mcf_set_timeout,
> +	.write_b = esdhc_mcf_writeb_be,
> +	.write_w = esdhc_mcf_writew_be,
> +	.write_l = esdhc_mcf_writel_be,
> +	.read_b = esdhc_mcf_readb_be,
> +	.read_w = esdhc_mcf_readw_be,
> +	.read_l = esdhc_mcf_readl_be,
> +	.copy_to_bounce_buffer = esdhc_mcf_copy_to_bounce_buffer,
> +	.request_done = esdhc_mcf_request_done,
> +};
> +
> +static const struct sdhci_pltfm_data sdhci_esdhc_mcf_pdata = {
> +	.ops = &sdhci_esdhc_ops,
> +	.quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_FORCE_DMA,
> +		 /*
> +		  * Mandatory quirk,
> +		  * controller does not support cmd23,
> +		  * without, on > 8G cards cmd23 is used, and
> +		  * driver times out.
> +		  */
> +		  SDHCI_QUIRK2_HOST_NO_CMD23,
> +};
> +
> +static int esdhc_mcf_plat_init(struct sdhci_host *host,
> +			       struct pltfm_mcf_data *mcf_data)
> +{
> +	struct mcf_esdhc_platform_data *plat_data;
> +
> +	if (!host->mmc->parent->platform_data) {
> +		dev_err(mmc_dev(host->mmc), "no platform data!\n");
> +		return -EINVAL;
> +	}
> +
> +	plat_data = (struct mcf_esdhc_platform_data *)
> +			host->mmc->parent->platform_data;
> +
> +	/* Card_detect */
> +	switch (plat_data->cd_type) {
> +	default:
> +	case ESDHC_CD_CONTROLLER:
> +		/* We have a working card_detect back */
> +		host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
> +		break;
> +	case ESDHC_CD_PERMANENT:
> +		host->mmc->caps |= MMC_CAP_NONREMOVABLE;
> +		break;
> +	case ESDHC_CD_NONE:
> +		break;
> +	}
> +
> +	switch (plat_data->max_bus_width) {
> +	case 4:
> +		host->mmc->caps |= MMC_CAP_4_BIT_DATA;
> +		break;
> +	case 1:
> +	default:
> +		host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int sdhci_esdhc_mcf_probe(struct platform_device *pdev)
> +{
> +	struct sdhci_host *host;
> +	struct sdhci_pltfm_host *pltfm_host;
> +	struct pltfm_mcf_data *mcf_data;
> +	int err;
> +
> +	host = sdhci_pltfm_init(pdev, &sdhci_esdhc_mcf_pdata,
> +				sizeof(*mcf_data));
> +
> +	if (IS_ERR(host))
> +		return PTR_ERR(host);
> +
> +	pltfm_host = sdhci_priv(host);
> +	mcf_data = sdhci_pltfm_priv(pltfm_host);
> +
> +	host->sdma_boundary = 0;
> +
> +	host->flags |= SDHCI_AUTO_CMD12;
> +
> +	mcf_data->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
> +	if (IS_ERR(mcf_data->clk_ipg)) {
> +		err = PTR_ERR(mcf_data->clk_ipg);
> +		goto err_exit;
> +	}
> +
> +	mcf_data->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
> +	if (IS_ERR(mcf_data->clk_ahb)) {
> +		err = PTR_ERR(mcf_data->clk_ahb);
> +		goto err_exit;
> +	}
> +
> +	mcf_data->clk_per = devm_clk_get(&pdev->dev, "per");
> +	if (IS_ERR(mcf_data->clk_per)) {
> +		err = PTR_ERR(mcf_data->clk_per);
> +		goto err_exit;
> +	}
> +
> +	pltfm_host->clk = mcf_data->clk_per;
> +	pltfm_host->clock = clk_get_rate(pltfm_host->clk);
> +	err = clk_prepare_enable(mcf_data->clk_per);
> +	if (err)
> +		goto err_exit;
> +
> +	err = clk_prepare_enable(mcf_data->clk_ipg);
> +	if (err)
> +		goto unprep_per;
> +
> +	err = clk_prepare_enable(mcf_data->clk_ahb);
> +	if (err)
> +		goto unprep_ipg;
> +
> +	err = esdhc_mcf_plat_init(host, mcf_data);
> +	if (err)
> +		goto unprep_ahb;
> +
> +	err = sdhci_setup_host(host);
> +	if (err)
> +		goto unprep_ahb;
> +
> +	if (!host->bounce_buffer) {
> +		dev_err(&pdev->dev, "bounce buffer not allocated");
> +		err = -ENOMEM;
> +		goto cleanup;
> +	}
> +
> +	err = __sdhci_add_host(host);
> +	if (err)
> +		goto cleanup;
> +
> +	return 0;
> +
> +cleanup:
> +	sdhci_cleanup_host(host);
> +unprep_ahb:
> +	clk_disable_unprepare(mcf_data->clk_ahb);
> +unprep_ipg:
> +	clk_disable_unprepare(mcf_data->clk_ipg);
> +unprep_per:
> +	clk_disable_unprepare(mcf_data->clk_per);
> +err_exit:
> +	sdhci_pltfm_free(pdev);
> +
> +	return err;
> +}
> +
> +static int sdhci_esdhc_mcf_remove(struct platform_device *pdev)
> +{
> +	struct sdhci_host *host = platform_get_drvdata(pdev);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> +
> +	sdhci_remove_host(host, 0);
> +
> +	clk_disable_unprepare(mcf_data->clk_ipg);
> +	clk_disable_unprepare(mcf_data->clk_ahb);
> +	clk_disable_unprepare(mcf_data->clk_per);
> +
> +	sdhci_pltfm_free(pdev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver sdhci_esdhc_mcf_driver = {
> +	.driver	= {
> +		.name = "sdhci-esdhc-mcf",
> +	},
> +	.probe = sdhci_esdhc_mcf_probe,
> +	.remove = sdhci_esdhc_mcf_remove,
> +};
> +
> +module_platform_driver(sdhci_esdhc_mcf_driver);
> +
> +MODULE_DESCRIPTION("SDHCI driver for Freescale ColdFire eSDHC");
> +MODULE_AUTHOR("Angelo Dureghello <angelo.dureghello@timesys.com>");
> +MODULE_LICENSE("GPL v2");
> 


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v5 3/3] mmc: host: add Coldfire esdhc support
  2020-05-16 18:09   ` Adrian Hunter
@ 2020-05-18  8:00     ` Angelo Dureghello
  2020-05-18  8:02       ` Ulf Hansson
  0 siblings, 1 reply; 7+ messages in thread
From: Angelo Dureghello @ 2020-05-18  8:00 UTC (permalink / raw)
  To: Adrian Hunter; +Cc: Ulf Hansson, Greg Ungerer, linux-mmc, linux-m68k

Hi Adrian and all,

thanks,

i can fix also these small issues in a v6 in case. Since is "acked" not sure
if i have to do it. Please let me know.

Also, checkpatch signals a missing maintainer name for this driver,
if there is any need to set a name for it ?

Regards,
Angelo

On Sat, May 16, 2020 at 8:09 PM Adrian Hunter <adrian.hunter@intel.com> wrote:
>
> On 16/05/20 1:27 am, Angelo Dureghello wrote:
> > This driver has been developed as a separate module starting
> > from the similar sdhci-esdhc-imx.c.
> >
> > Reasons for a separate sdchi-esdhc-mcf driver:
> >
> > - m68K architecture does not support devicetrees, so modifying
> > sdhci-of-esdhc.c that is devicetree-related adding platform data
> > seems not appropriate,
> > - clock-related part, has to be implemented specifically for
> > mcf5441x family (see esdhc_mcf_pltfm_set_clock()),
> > - this is a big endian cpu accessing a big endian controller,
> > but about sdma, this controller does not support hw swap, which
> > needs to be handled with specific code,
> > - some other minor differences but mainly to avoid risks on
> > tweaking inside largely used imx driver. Adding just a small
> > size ColdFire-specific driver, with benefits in a further less
> > risky maintenance.
> >
> > Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com>
>
> One minor comment below, nevertheless:
>
> Acked-by: Adrian Hunter <adrian.hunter@intel.com>
>
> > ---
> > Changes for v3:
> > - fix write support
> > Changes for v4:
> > none
> > Changes for v5:
> > - better probe cleanup on errors
> > - test for bounce buffer to be allocated
> > - rename pre_dma_transfer to copy_to_bounce_buffer
> > - change swap to swap and copy to bounce buffer
> > - fix line alignments to pass checkpatch --strict
> > ---
> >  drivers/mmc/host/Kconfig           |  13 +
> >  drivers/mmc/host/Makefile          |   1 +
> >  drivers/mmc/host/sdhci-esdhc-mcf.c | 522 +++++++++++++++++++++++++++++
> >  3 files changed, 536 insertions(+)
> >  create mode 100644 drivers/mmc/host/sdhci-esdhc-mcf.c
> >
> > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> > index 462b5352fea7..da793fc95203 100644
> > --- a/drivers/mmc/host/Kconfig
> > +++ b/drivers/mmc/host/Kconfig
> > @@ -235,6 +235,19 @@ config MMC_SDHCI_CNS3XXX
> >
> >         If unsure, say N.
> >
> > +config MMC_SDHCI_ESDHC_MCF
> > +     tristate "SDHCI support for the Freescale eSDHC ColdFire controller"
> > +     depends on M5441x
> > +     depends on MMC_SDHCI_PLTFM
> > +     select MMC_SDHCI_IO_ACCESSORS
> > +     help
> > +       This selects the Freescale eSDHC controller support for
> > +       ColdFire mcf5441x devices.
> > +
> > +       If you have a controller with this interface, say Y or M here.
> > +
> > +       If unsure, say N.
> > +
> >  config MMC_SDHCI_ESDHC_IMX
> >       tristate "SDHCI support for the Freescale eSDHC/uSDHC i.MX controller"
> >       depends on ARCH_MXC
> > diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> > index b929ef941208..af2cdaadc4d3 100644
> > --- a/drivers/mmc/host/Makefile
> > +++ b/drivers/mmc/host/Makefile
> > @@ -82,6 +82,7 @@ obj-$(CONFIG_MMC_REALTEK_USB)       += rtsx_usb_sdmmc.o
> >  obj-$(CONFIG_MMC_SDHCI_PLTFM)                += sdhci-pltfm.o
> >  obj-$(CONFIG_MMC_SDHCI_CADENCE)              += sdhci-cadence.o
> >  obj-$(CONFIG_MMC_SDHCI_CNS3XXX)              += sdhci-cns3xxx.o
> > +obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)       += sdhci-esdhc-mcf.o
> >  obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX)    += sdhci-esdhc-imx.o
> >  obj-$(CONFIG_MMC_SDHCI_DOVE)         += sdhci-dove.o
> >  obj-$(CONFIG_MMC_SDHCI_TEGRA)                += sdhci-tegra.o
> > diff --git a/drivers/mmc/host/sdhci-esdhc-mcf.c b/drivers/mmc/host/sdhci-esdhc-mcf.c
> > new file mode 100644
> > index 000000000000..445faf76e676
> > --- /dev/null
> > +++ b/drivers/mmc/host/sdhci-esdhc-mcf.c
> > @@ -0,0 +1,522 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Freescale eSDHC ColdFire family controller driver, platform bus.
> > + *
> > + * Copyright (c) 2020 Timesys Corporation
> > + *   Author: Angelo Dureghello <angelo.dureghello@timesys.it>
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/delay.h>
> > +#include <linux/platform_data/mmc-esdhc-mcf.h>
> > +#include <linux/mmc/mmc.h>
> > +#include "sdhci-pltfm.h"
> > +#include "sdhci-esdhc.h"
> > +
> > +#define      ESDHC_PROCTL_D3CD               0x08
> > +#define ESDHC_SYS_CTRL_DTOCV_MASK    0x0f
> > +#define ESDHC_DEFAULT_HOST_CONTROL   0x28
> > +
> > +/*
> > + * Freescale eSDHC has DMA ERR flag at bit 28, not as std spec says, bit 25.
> > + */
> > +#define ESDHC_INT_VENDOR_SPEC_DMA_ERR        BIT(28)
> > +
> > +struct pltfm_mcf_data {
> > +     struct clk *clk_ipg;
> > +     struct clk *clk_ahb;
> > +     struct clk *clk_per;
> > +     int aside;
> > +     int current_bus_width;
> > +};
> > +
> > +static inline void esdhc_mcf_buffer_swap32(u32 *buf, int len)
> > +{
> > +     int i;
> > +     u32 temp;
> > +
> > +     len = (len + 3) >> 2;
> > +
> > +     for (i = 0; i < len;  i++) {
> > +             temp = swab32(*buf);
> > +             *buf++ = temp;
> > +     }
> > +}
> > +
> > +static inline void esdhc_clrset_be(struct sdhci_host *host,
> > +                                u32 mask, u32 val, int reg)
> > +{
> > +     void __iomem *base = host->ioaddr + (reg & ~3);
> > +     u8 shift = (reg & 3) << 3;
> > +
> > +     mask <<= shift;
> > +     val <<= shift;
> > +
> > +     if (reg == SDHCI_HOST_CONTROL)
> > +             val |= ESDHC_PROCTL_D3CD;
> > +
> > +     writel((readl(base) & ~mask) | val, base);
> > +}
> > +
> > +/*
> > + * Note: mcf is big-endian, single bytes need to be accessed at big endian
> > + * offsets.
> > + */
> > +static void esdhc_mcf_writeb_be(struct sdhci_host *host, u8 val, int reg)
> > +{
> > +     void __iomem *base = host->ioaddr + (reg & ~3);
> > +     u8 shift = (reg & 3) << 3;
> > +     u32 mask = ~(0xff << shift);
> > +
> > +     if (reg == SDHCI_HOST_CONTROL) {
> > +             u32 host_ctrl = ESDHC_DEFAULT_HOST_CONTROL;
> > +             u8 dma_bits = (val & SDHCI_CTRL_DMA_MASK) >> 3;
> > +             u8 tmp = readb(host->ioaddr + SDHCI_HOST_CONTROL + 1);
> > +
> > +             tmp &= ~0x03;
> > +             tmp |= dma_bits;
> > +
> > +             /*
> > +              * Recomposition needed, restore always endianness and
> > +              * keep D3CD and AI, just setting bus width.
> > +              */
> > +             host_ctrl |= val;
> > +             host_ctrl |= (dma_bits << 8);
> > +             writel(host_ctrl, host->ioaddr + SDHCI_HOST_CONTROL);
> > +
> > +             return;
> > +     }
> > +
> > +     writel((readl(base) & mask) | (val << shift), base);
> > +}
> > +
> > +static void esdhc_mcf_writew_be(struct sdhci_host *host, u16 val, int reg)
> > +{
> > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > +     struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> > +     void __iomem *base = host->ioaddr + (reg & ~3);
> > +     u8 shift = (reg & 3) << 3;
> > +     u32 mask = ~(0xffff << shift);
> > +
> > +     switch (reg) {
> > +     case SDHCI_TRANSFER_MODE:
> > +             mcf_data->aside = val;
> > +             return;
> > +     case SDHCI_COMMAND:
> > +             if (host->cmd->opcode == MMC_STOP_TRANSMISSION)
> > +                     val |= SDHCI_CMD_ABORTCMD;
> > +
> > +             /*
> > +              * As for the fsl driver,
> > +              * we have to set the mode in a single write here.
> > +              */
> > +             writel(val << 16 | mcf_data->aside,
> > +                    host->ioaddr + SDHCI_TRANSFER_MODE);
> > +             return;
> > +     }
> > +
> > +     writel((readl(base) & mask) | (val << shift), base);
> > +}
> > +
> > +static void esdhc_mcf_writel_be(struct sdhci_host *host, u32 val, int reg)
> > +{
> > +     writel(val, host->ioaddr + reg);
> > +}
> > +
> > +static u8 esdhc_mcf_readb_be(struct sdhci_host *host, int reg)
> > +{
> > +     if (reg == SDHCI_HOST_CONTROL) {
> > +             u8 __iomem *base = host->ioaddr + (reg & ~3);
> > +             u16 val = readw(base + 2);
> > +             u8 dma_bits = (val >> 5) & SDHCI_CTRL_DMA_MASK;
> > +             u8 host_ctrl = val & 0xff;
> > +
> > +             host_ctrl &= ~SDHCI_CTRL_DMA_MASK;
> > +             host_ctrl |= dma_bits;
> > +
> > +             return host_ctrl;
> > +     }
> > +
> > +     return readb(host->ioaddr + (reg ^ 0x3));
> > +}
> > +
> > +static u16 esdhc_mcf_readw_be(struct sdhci_host *host, int reg)
> > +{
> > +     /*
> > +      * For SDHCI_HOST_VERSION, sdhci specs defines 0xFE,
> > +      * a wrong offset for us, we are at 0xFC.
> > +      */
> > +     if (reg == SDHCI_HOST_VERSION)
> > +             reg -= 2;
> > +
> > +     return readw(host->ioaddr + (reg ^ 0x2));
> > +}
> > +
> > +static u32 esdhc_mcf_readl_be(struct sdhci_host *host, int reg)
> > +{
> > +     u32 val;
> > +
> > +     val = readl(host->ioaddr + reg);
> > +
> > +     /*
> > +      * RM (25.3.9) sd pin clock must never exceed 25Mhz.
> > +      * So forcing legacy mode at 25Mhz.
> > +      */
> > +     if (unlikely(reg == SDHCI_CAPABILITIES))
> > +             val &= ~SDHCI_CAN_DO_HISPD;
> > +
> > +     if (unlikely(reg == SDHCI_INT_STATUS)) {
> > +             if (val & ESDHC_INT_VENDOR_SPEC_DMA_ERR) {
> > +                     val &= ~ESDHC_INT_VENDOR_SPEC_DMA_ERR;
> > +                     val |= SDHCI_INT_ADMA_ERROR;
> > +             }
> > +     }
> > +
> > +     return val;
> > +}
> > +
> > +static unsigned int esdhc_mcf_get_max_timeout_count(struct sdhci_host *host)
> > +{
> > +     return 1 << 27;
> > +}
> > +
> > +static void esdhc_mcf_set_timeout(struct sdhci_host *host,
> > +                               struct mmc_command *cmd)
> > +{
> > +     /* Use maximum timeout counter */
> > +     esdhc_clrset_be(host, ESDHC_SYS_CTRL_DTOCV_MASK, 0xE,
> > +                     SDHCI_TIMEOUT_CONTROL);
> > +}
> > +
> > +static void esdhc_mcf_reset(struct sdhci_host *host, u8 mask)
> > +{
> > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > +     struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> > +
> > +     sdhci_reset(host, mask);
> > +
> > +     esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
> > +                     mcf_data->current_bus_width, SDHCI_HOST_CONTROL);
> > +
> > +     sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
> > +     sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
> > +}
> > +
> > +static unsigned int esdhc_mcf_pltfm_get_max_clock(struct sdhci_host *host)
> > +{
> > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > +
> > +     return pltfm_host->clock;
> > +}
> > +
> > +static unsigned int esdhc_mcf_pltfm_get_min_clock(struct sdhci_host *host)
> > +{
> > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > +
> > +     return pltfm_host->clock / 256 / 16;
> > +}
> > +
> > +static void esdhc_mcf_pltfm_set_clock(struct sdhci_host *host,
> > +                                   unsigned int clock)
> > +{
> > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > +     unsigned long *pll_dr = (unsigned long *)MCF_PLL_DR;
> > +     u32 fvco, fsys, fesdhc, temp;
> > +     const int sdclkfs[] = {2, 4, 8, 16, 32, 64, 128, 256};
> > +     int delta, old_delta = clock;
> > +     int i, q, ri, rq;
> > +
> > +     if (clock == 0) {
> > +             host->mmc->actual_clock = 0;
> > +             return;
> > +     }
> > +
> > +     /*
> > +      * ColdFire eSDHC clock.s
> > +      *
> > +      * pll -+-> / outdiv1 --> fsys
> > +      *      +-> / outdiv3 --> eSDHC clock ---> / SDCCLKFS / DVS
> > +      *
> > +      * mcf5441x datasheet says:
> > +      * (8.1.2) eSDHC should be 40 MHz max
> > +      * (25.3.9) eSDHC input is, as example, 96 Mhz ...
> > +      * (25.3.9) sd pin clock must never exceed 25Mhz
> > +      *
> > +      * fvco = fsys * outdvi1 + 1
> > +      * fshdc = fvco / outdiv3 + 1
> > +      */
> > +     temp = readl(pll_dr);
> > +     fsys = pltfm_host->clock;
> > +     fvco = fsys * ((temp & 0x1f) + 1);
> > +     fesdhc = fvco / (((temp >> 10) & 0x1f) + 1);
> > +
> > +     for (i = 0; i < 8; ++i) {
> > +             int result = fesdhc / sdclkfs[i];
> > +
> > +             for (q = 1; q < 17; ++q) {
> > +                     int finale = result / q;
> > +
> > +                     delta = abs(clock - finale);
> > +
> > +                     if (delta < old_delta) {
> > +                             old_delta = delta;
> > +                             ri = i;
> > +                             rq = q;
> > +                     }
> > +             }
> > +     }
> > +
> > +     /*
> > +      * Apply divisors and re-enable all the clocks
> > +      */
> > +     temp = ((sdclkfs[ri] >> 1) << 8) | ((rq - 1) << 4) |
> > +             (ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN);
> > +     esdhc_clrset_be(host, 0x0000fff7, temp, SDHCI_CLOCK_CONTROL);
> > +
> > +     host->mmc->actual_clock = clock;
> > +
> > +     mdelay(1);
> > +}
> > +
> > +static void esdhc_mcf_pltfm_set_bus_width(struct sdhci_host *host, int width)
> > +{
> > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > +     struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> > +
> > +     switch (width) {
> > +     case MMC_BUS_WIDTH_4:
> > +             mcf_data->current_bus_width = ESDHC_CTRL_4BITBUS;
> > +             break;
> > +     default:
> > +             mcf_data->current_bus_width = 0;
> > +             break;
> > +     }
> > +
> > +     esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
> > +                     mcf_data->current_bus_width, SDHCI_HOST_CONTROL);
> > +}
> > +
> > +static void esdhc_mcf_request_done(struct sdhci_host *host,
> > +                                struct mmc_request *mrq)
> > +{
> > +     struct scatterlist *sg;
> > +     u32 *buffer;
> > +     int i;
> > +
> > +     if (!mrq->data || !mrq->data->bytes_xfered)
> > +             goto exit_done;
> > +
> > +     if (mmc_get_dma_dir(mrq->data) != DMA_FROM_DEVICE)
> > +             goto exit_done;
> > +
> > +     /*
> > +      * On mcf5441x there is no hw sdma option/flag to select the dma
> > +      * transfer endiannes. A swap after the transfer is needed.
> > +      */
> > +     for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i) {
> > +             buffer = (u32 *)sg_virt(sg);
> > +             esdhc_mcf_buffer_swap32(buffer, sg->length);
> > +     }
> > +
> > +exit_done:
> > +     mmc_request_done(host->mmc, mrq);
> > +}
> > +
> > +static void esdhc_mcf_copy_to_bounce_buffer(struct sdhci_host *host,
> > +                                         struct mmc_data *data)
> > +{
> > +     unsigned int length = data->blksz * data->blocks;
> > +
> > +     sg_copy_to_buffer(data->sg, data->sg_len,
> > +                       host->bounce_buffer, length);
> > +
> > +     esdhc_mcf_buffer_swap32((u32 *)host->bounce_buffer,
> > +                             data->blksz * data->blocks);
>
> data->blksz * data->blocks is already calculated as 'length'
>
> > +}
> > +
> > +static struct sdhci_ops sdhci_esdhc_ops = {
> > +     .reset = esdhc_mcf_reset,
> > +     .set_clock = esdhc_mcf_pltfm_set_clock,
> > +     .get_max_clock = esdhc_mcf_pltfm_get_max_clock,
> > +     .get_min_clock = esdhc_mcf_pltfm_get_min_clock,
> > +     .set_bus_width = esdhc_mcf_pltfm_set_bus_width,
> > +     .get_max_timeout_count = esdhc_mcf_get_max_timeout_count,
> > +     .set_timeout = esdhc_mcf_set_timeout,
> > +     .write_b = esdhc_mcf_writeb_be,
> > +     .write_w = esdhc_mcf_writew_be,
> > +     .write_l = esdhc_mcf_writel_be,
> > +     .read_b = esdhc_mcf_readb_be,
> > +     .read_w = esdhc_mcf_readw_be,
> > +     .read_l = esdhc_mcf_readl_be,
> > +     .copy_to_bounce_buffer = esdhc_mcf_copy_to_bounce_buffer,
> > +     .request_done = esdhc_mcf_request_done,
> > +};
> > +
> > +static const struct sdhci_pltfm_data sdhci_esdhc_mcf_pdata = {
> > +     .ops = &sdhci_esdhc_ops,
> > +     .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_FORCE_DMA,
> > +              /*
> > +               * Mandatory quirk,
> > +               * controller does not support cmd23,
> > +               * without, on > 8G cards cmd23 is used, and
> > +               * driver times out.
> > +               */
> > +               SDHCI_QUIRK2_HOST_NO_CMD23,
> > +};
> > +
> > +static int esdhc_mcf_plat_init(struct sdhci_host *host,
> > +                            struct pltfm_mcf_data *mcf_data)
> > +{
> > +     struct mcf_esdhc_platform_data *plat_data;
> > +
> > +     if (!host->mmc->parent->platform_data) {
> > +             dev_err(mmc_dev(host->mmc), "no platform data!\n");
> > +             return -EINVAL;
> > +     }
> > +
> > +     plat_data = (struct mcf_esdhc_platform_data *)
> > +                     host->mmc->parent->platform_data;
> > +
> > +     /* Card_detect */
> > +     switch (plat_data->cd_type) {
> > +     default:
> > +     case ESDHC_CD_CONTROLLER:
> > +             /* We have a working card_detect back */
> > +             host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
> > +             break;
> > +     case ESDHC_CD_PERMANENT:
> > +             host->mmc->caps |= MMC_CAP_NONREMOVABLE;
> > +             break;
> > +     case ESDHC_CD_NONE:
> > +             break;
> > +     }
> > +
> > +     switch (plat_data->max_bus_width) {
> > +     case 4:
> > +             host->mmc->caps |= MMC_CAP_4_BIT_DATA;
> > +             break;
> > +     case 1:
> > +     default:
> > +             host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
> > +             break;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static int sdhci_esdhc_mcf_probe(struct platform_device *pdev)
> > +{
> > +     struct sdhci_host *host;
> > +     struct sdhci_pltfm_host *pltfm_host;
> > +     struct pltfm_mcf_data *mcf_data;
> > +     int err;
> > +
> > +     host = sdhci_pltfm_init(pdev, &sdhci_esdhc_mcf_pdata,
> > +                             sizeof(*mcf_data));
> > +
> > +     if (IS_ERR(host))
> > +             return PTR_ERR(host);
> > +
> > +     pltfm_host = sdhci_priv(host);
> > +     mcf_data = sdhci_pltfm_priv(pltfm_host);
> > +
> > +     host->sdma_boundary = 0;
> > +
> > +     host->flags |= SDHCI_AUTO_CMD12;
> > +
> > +     mcf_data->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
> > +     if (IS_ERR(mcf_data->clk_ipg)) {
> > +             err = PTR_ERR(mcf_data->clk_ipg);
> > +             goto err_exit;
> > +     }
> > +
> > +     mcf_data->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
> > +     if (IS_ERR(mcf_data->clk_ahb)) {
> > +             err = PTR_ERR(mcf_data->clk_ahb);
> > +             goto err_exit;
> > +     }
> > +
> > +     mcf_data->clk_per = devm_clk_get(&pdev->dev, "per");
> > +     if (IS_ERR(mcf_data->clk_per)) {
> > +             err = PTR_ERR(mcf_data->clk_per);
> > +             goto err_exit;
> > +     }
> > +
> > +     pltfm_host->clk = mcf_data->clk_per;
> > +     pltfm_host->clock = clk_get_rate(pltfm_host->clk);
> > +     err = clk_prepare_enable(mcf_data->clk_per);
> > +     if (err)
> > +             goto err_exit;
> > +
> > +     err = clk_prepare_enable(mcf_data->clk_ipg);
> > +     if (err)
> > +             goto unprep_per;
> > +
> > +     err = clk_prepare_enable(mcf_data->clk_ahb);
> > +     if (err)
> > +             goto unprep_ipg;
> > +
> > +     err = esdhc_mcf_plat_init(host, mcf_data);
> > +     if (err)
> > +             goto unprep_ahb;
> > +
> > +     err = sdhci_setup_host(host);
> > +     if (err)
> > +             goto unprep_ahb;
> > +
> > +     if (!host->bounce_buffer) {
> > +             dev_err(&pdev->dev, "bounce buffer not allocated");
> > +             err = -ENOMEM;
> > +             goto cleanup;
> > +     }
> > +
> > +     err = __sdhci_add_host(host);
> > +     if (err)
> > +             goto cleanup;
> > +
> > +     return 0;
> > +
> > +cleanup:
> > +     sdhci_cleanup_host(host);
> > +unprep_ahb:
> > +     clk_disable_unprepare(mcf_data->clk_ahb);
> > +unprep_ipg:
> > +     clk_disable_unprepare(mcf_data->clk_ipg);
> > +unprep_per:
> > +     clk_disable_unprepare(mcf_data->clk_per);
> > +err_exit:
> > +     sdhci_pltfm_free(pdev);
> > +
> > +     return err;
> > +}
> > +
> > +static int sdhci_esdhc_mcf_remove(struct platform_device *pdev)
> > +{
> > +     struct sdhci_host *host = platform_get_drvdata(pdev);
> > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > +     struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> > +
> > +     sdhci_remove_host(host, 0);
> > +
> > +     clk_disable_unprepare(mcf_data->clk_ipg);
> > +     clk_disable_unprepare(mcf_data->clk_ahb);
> > +     clk_disable_unprepare(mcf_data->clk_per);
> > +
> > +     sdhci_pltfm_free(pdev);
> > +
> > +     return 0;
> > +}
> > +
> > +static struct platform_driver sdhci_esdhc_mcf_driver = {
> > +     .driver = {
> > +             .name = "sdhci-esdhc-mcf",
> > +     },
> > +     .probe = sdhci_esdhc_mcf_probe,
> > +     .remove = sdhci_esdhc_mcf_remove,
> > +};
> > +
> > +module_platform_driver(sdhci_esdhc_mcf_driver);
> > +
> > +MODULE_DESCRIPTION("SDHCI driver for Freescale ColdFire eSDHC");
> > +MODULE_AUTHOR("Angelo Dureghello <angelo.dureghello@timesys.com>");
> > +MODULE_LICENSE("GPL v2");
> >
>


-- 
Angelo Dureghello
Timesys
e. angelo.dureghello@timesys.com

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v5 3/3] mmc: host: add Coldfire esdhc support
  2020-05-18  8:00     ` Angelo Dureghello
@ 2020-05-18  8:02       ` Ulf Hansson
  0 siblings, 0 replies; 7+ messages in thread
From: Ulf Hansson @ 2020-05-18  8:02 UTC (permalink / raw)
  To: Angelo Dureghello; +Cc: Adrian Hunter, Greg Ungerer, linux-mmc, linux-m68k

On Mon, 18 May 2020 at 09:55, Angelo Dureghello
<angelo.dureghello@timesys.com> wrote:
>
> Hi Adrian and all,
>
> thanks,
>
> i can fix also these small issues in a v6 in case. Since is "acked" not sure
> if i have to do it. Please let me know.

Please re-spin a v6 to address Adrian's comments and add his acks.

In some cases I am amending the patches while applying, but this time
it looks like it's easier for me to get a re-spin.

>
> Also, checkpatch signals a missing maintainer name for this driver,
> if there is any need to set a name for it ?

If you are willing to look after it, please add a patch adding
yourself as maintainer!

Otherwise I think the generic SDHCI section covers it.

Kind regards
Uffe

>
> Regards,
> Angelo
>
> On Sat, May 16, 2020 at 8:09 PM Adrian Hunter <adrian.hunter@intel.com> wrote:
> >
> > On 16/05/20 1:27 am, Angelo Dureghello wrote:
> > > This driver has been developed as a separate module starting
> > > from the similar sdhci-esdhc-imx.c.
> > >
> > > Reasons for a separate sdchi-esdhc-mcf driver:
> > >
> > > - m68K architecture does not support devicetrees, so modifying
> > > sdhci-of-esdhc.c that is devicetree-related adding platform data
> > > seems not appropriate,
> > > - clock-related part, has to be implemented specifically for
> > > mcf5441x family (see esdhc_mcf_pltfm_set_clock()),
> > > - this is a big endian cpu accessing a big endian controller,
> > > but about sdma, this controller does not support hw swap, which
> > > needs to be handled with specific code,
> > > - some other minor differences but mainly to avoid risks on
> > > tweaking inside largely used imx driver. Adding just a small
> > > size ColdFire-specific driver, with benefits in a further less
> > > risky maintenance.
> > >
> > > Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com>
> >
> > One minor comment below, nevertheless:
> >
> > Acked-by: Adrian Hunter <adrian.hunter@intel.com>
> >
> > > ---
> > > Changes for v3:
> > > - fix write support
> > > Changes for v4:
> > > none
> > > Changes for v5:
> > > - better probe cleanup on errors
> > > - test for bounce buffer to be allocated
> > > - rename pre_dma_transfer to copy_to_bounce_buffer
> > > - change swap to swap and copy to bounce buffer
> > > - fix line alignments to pass checkpatch --strict
> > > ---
> > >  drivers/mmc/host/Kconfig           |  13 +
> > >  drivers/mmc/host/Makefile          |   1 +
> > >  drivers/mmc/host/sdhci-esdhc-mcf.c | 522 +++++++++++++++++++++++++++++
> > >  3 files changed, 536 insertions(+)
> > >  create mode 100644 drivers/mmc/host/sdhci-esdhc-mcf.c
> > >
> > > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> > > index 462b5352fea7..da793fc95203 100644
> > > --- a/drivers/mmc/host/Kconfig
> > > +++ b/drivers/mmc/host/Kconfig
> > > @@ -235,6 +235,19 @@ config MMC_SDHCI_CNS3XXX
> > >
> > >         If unsure, say N.
> > >
> > > +config MMC_SDHCI_ESDHC_MCF
> > > +     tristate "SDHCI support for the Freescale eSDHC ColdFire controller"
> > > +     depends on M5441x
> > > +     depends on MMC_SDHCI_PLTFM
> > > +     select MMC_SDHCI_IO_ACCESSORS
> > > +     help
> > > +       This selects the Freescale eSDHC controller support for
> > > +       ColdFire mcf5441x devices.
> > > +
> > > +       If you have a controller with this interface, say Y or M here.
> > > +
> > > +       If unsure, say N.
> > > +
> > >  config MMC_SDHCI_ESDHC_IMX
> > >       tristate "SDHCI support for the Freescale eSDHC/uSDHC i.MX controller"
> > >       depends on ARCH_MXC
> > > diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> > > index b929ef941208..af2cdaadc4d3 100644
> > > --- a/drivers/mmc/host/Makefile
> > > +++ b/drivers/mmc/host/Makefile
> > > @@ -82,6 +82,7 @@ obj-$(CONFIG_MMC_REALTEK_USB)       += rtsx_usb_sdmmc.o
> > >  obj-$(CONFIG_MMC_SDHCI_PLTFM)                += sdhci-pltfm.o
> > >  obj-$(CONFIG_MMC_SDHCI_CADENCE)              += sdhci-cadence.o
> > >  obj-$(CONFIG_MMC_SDHCI_CNS3XXX)              += sdhci-cns3xxx.o
> > > +obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)       += sdhci-esdhc-mcf.o
> > >  obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX)    += sdhci-esdhc-imx.o
> > >  obj-$(CONFIG_MMC_SDHCI_DOVE)         += sdhci-dove.o
> > >  obj-$(CONFIG_MMC_SDHCI_TEGRA)                += sdhci-tegra.o
> > > diff --git a/drivers/mmc/host/sdhci-esdhc-mcf.c b/drivers/mmc/host/sdhci-esdhc-mcf.c
> > > new file mode 100644
> > > index 000000000000..445faf76e676
> > > --- /dev/null
> > > +++ b/drivers/mmc/host/sdhci-esdhc-mcf.c
> > > @@ -0,0 +1,522 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Freescale eSDHC ColdFire family controller driver, platform bus.
> > > + *
> > > + * Copyright (c) 2020 Timesys Corporation
> > > + *   Author: Angelo Dureghello <angelo.dureghello@timesys.it>
> > > + */
> > > +
> > > +#include <linux/module.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/platform_data/mmc-esdhc-mcf.h>
> > > +#include <linux/mmc/mmc.h>
> > > +#include "sdhci-pltfm.h"
> > > +#include "sdhci-esdhc.h"
> > > +
> > > +#define      ESDHC_PROCTL_D3CD               0x08
> > > +#define ESDHC_SYS_CTRL_DTOCV_MASK    0x0f
> > > +#define ESDHC_DEFAULT_HOST_CONTROL   0x28
> > > +
> > > +/*
> > > + * Freescale eSDHC has DMA ERR flag at bit 28, not as std spec says, bit 25.
> > > + */
> > > +#define ESDHC_INT_VENDOR_SPEC_DMA_ERR        BIT(28)
> > > +
> > > +struct pltfm_mcf_data {
> > > +     struct clk *clk_ipg;
> > > +     struct clk *clk_ahb;
> > > +     struct clk *clk_per;
> > > +     int aside;
> > > +     int current_bus_width;
> > > +};
> > > +
> > > +static inline void esdhc_mcf_buffer_swap32(u32 *buf, int len)
> > > +{
> > > +     int i;
> > > +     u32 temp;
> > > +
> > > +     len = (len + 3) >> 2;
> > > +
> > > +     for (i = 0; i < len;  i++) {
> > > +             temp = swab32(*buf);
> > > +             *buf++ = temp;
> > > +     }
> > > +}
> > > +
> > > +static inline void esdhc_clrset_be(struct sdhci_host *host,
> > > +                                u32 mask, u32 val, int reg)
> > > +{
> > > +     void __iomem *base = host->ioaddr + (reg & ~3);
> > > +     u8 shift = (reg & 3) << 3;
> > > +
> > > +     mask <<= shift;
> > > +     val <<= shift;
> > > +
> > > +     if (reg == SDHCI_HOST_CONTROL)
> > > +             val |= ESDHC_PROCTL_D3CD;
> > > +
> > > +     writel((readl(base) & ~mask) | val, base);
> > > +}
> > > +
> > > +/*
> > > + * Note: mcf is big-endian, single bytes need to be accessed at big endian
> > > + * offsets.
> > > + */
> > > +static void esdhc_mcf_writeb_be(struct sdhci_host *host, u8 val, int reg)
> > > +{
> > > +     void __iomem *base = host->ioaddr + (reg & ~3);
> > > +     u8 shift = (reg & 3) << 3;
> > > +     u32 mask = ~(0xff << shift);
> > > +
> > > +     if (reg == SDHCI_HOST_CONTROL) {
> > > +             u32 host_ctrl = ESDHC_DEFAULT_HOST_CONTROL;
> > > +             u8 dma_bits = (val & SDHCI_CTRL_DMA_MASK) >> 3;
> > > +             u8 tmp = readb(host->ioaddr + SDHCI_HOST_CONTROL + 1);
> > > +
> > > +             tmp &= ~0x03;
> > > +             tmp |= dma_bits;
> > > +
> > > +             /*
> > > +              * Recomposition needed, restore always endianness and
> > > +              * keep D3CD and AI, just setting bus width.
> > > +              */
> > > +             host_ctrl |= val;
> > > +             host_ctrl |= (dma_bits << 8);
> > > +             writel(host_ctrl, host->ioaddr + SDHCI_HOST_CONTROL);
> > > +
> > > +             return;
> > > +     }
> > > +
> > > +     writel((readl(base) & mask) | (val << shift), base);
> > > +}
> > > +
> > > +static void esdhc_mcf_writew_be(struct sdhci_host *host, u16 val, int reg)
> > > +{
> > > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > > +     struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> > > +     void __iomem *base = host->ioaddr + (reg & ~3);
> > > +     u8 shift = (reg & 3) << 3;
> > > +     u32 mask = ~(0xffff << shift);
> > > +
> > > +     switch (reg) {
> > > +     case SDHCI_TRANSFER_MODE:
> > > +             mcf_data->aside = val;
> > > +             return;
> > > +     case SDHCI_COMMAND:
> > > +             if (host->cmd->opcode == MMC_STOP_TRANSMISSION)
> > > +                     val |= SDHCI_CMD_ABORTCMD;
> > > +
> > > +             /*
> > > +              * As for the fsl driver,
> > > +              * we have to set the mode in a single write here.
> > > +              */
> > > +             writel(val << 16 | mcf_data->aside,
> > > +                    host->ioaddr + SDHCI_TRANSFER_MODE);
> > > +             return;
> > > +     }
> > > +
> > > +     writel((readl(base) & mask) | (val << shift), base);
> > > +}
> > > +
> > > +static void esdhc_mcf_writel_be(struct sdhci_host *host, u32 val, int reg)
> > > +{
> > > +     writel(val, host->ioaddr + reg);
> > > +}
> > > +
> > > +static u8 esdhc_mcf_readb_be(struct sdhci_host *host, int reg)
> > > +{
> > > +     if (reg == SDHCI_HOST_CONTROL) {
> > > +             u8 __iomem *base = host->ioaddr + (reg & ~3);
> > > +             u16 val = readw(base + 2);
> > > +             u8 dma_bits = (val >> 5) & SDHCI_CTRL_DMA_MASK;
> > > +             u8 host_ctrl = val & 0xff;
> > > +
> > > +             host_ctrl &= ~SDHCI_CTRL_DMA_MASK;
> > > +             host_ctrl |= dma_bits;
> > > +
> > > +             return host_ctrl;
> > > +     }
> > > +
> > > +     return readb(host->ioaddr + (reg ^ 0x3));
> > > +}
> > > +
> > > +static u16 esdhc_mcf_readw_be(struct sdhci_host *host, int reg)
> > > +{
> > > +     /*
> > > +      * For SDHCI_HOST_VERSION, sdhci specs defines 0xFE,
> > > +      * a wrong offset for us, we are at 0xFC.
> > > +      */
> > > +     if (reg == SDHCI_HOST_VERSION)
> > > +             reg -= 2;
> > > +
> > > +     return readw(host->ioaddr + (reg ^ 0x2));
> > > +}
> > > +
> > > +static u32 esdhc_mcf_readl_be(struct sdhci_host *host, int reg)
> > > +{
> > > +     u32 val;
> > > +
> > > +     val = readl(host->ioaddr + reg);
> > > +
> > > +     /*
> > > +      * RM (25.3.9) sd pin clock must never exceed 25Mhz.
> > > +      * So forcing legacy mode at 25Mhz.
> > > +      */
> > > +     if (unlikely(reg == SDHCI_CAPABILITIES))
> > > +             val &= ~SDHCI_CAN_DO_HISPD;
> > > +
> > > +     if (unlikely(reg == SDHCI_INT_STATUS)) {
> > > +             if (val & ESDHC_INT_VENDOR_SPEC_DMA_ERR) {
> > > +                     val &= ~ESDHC_INT_VENDOR_SPEC_DMA_ERR;
> > > +                     val |= SDHCI_INT_ADMA_ERROR;
> > > +             }
> > > +     }
> > > +
> > > +     return val;
> > > +}
> > > +
> > > +static unsigned int esdhc_mcf_get_max_timeout_count(struct sdhci_host *host)
> > > +{
> > > +     return 1 << 27;
> > > +}
> > > +
> > > +static void esdhc_mcf_set_timeout(struct sdhci_host *host,
> > > +                               struct mmc_command *cmd)
> > > +{
> > > +     /* Use maximum timeout counter */
> > > +     esdhc_clrset_be(host, ESDHC_SYS_CTRL_DTOCV_MASK, 0xE,
> > > +                     SDHCI_TIMEOUT_CONTROL);
> > > +}
> > > +
> > > +static void esdhc_mcf_reset(struct sdhci_host *host, u8 mask)
> > > +{
> > > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > > +     struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> > > +
> > > +     sdhci_reset(host, mask);
> > > +
> > > +     esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
> > > +                     mcf_data->current_bus_width, SDHCI_HOST_CONTROL);
> > > +
> > > +     sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
> > > +     sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
> > > +}
> > > +
> > > +static unsigned int esdhc_mcf_pltfm_get_max_clock(struct sdhci_host *host)
> > > +{
> > > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > > +
> > > +     return pltfm_host->clock;
> > > +}
> > > +
> > > +static unsigned int esdhc_mcf_pltfm_get_min_clock(struct sdhci_host *host)
> > > +{
> > > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > > +
> > > +     return pltfm_host->clock / 256 / 16;
> > > +}
> > > +
> > > +static void esdhc_mcf_pltfm_set_clock(struct sdhci_host *host,
> > > +                                   unsigned int clock)
> > > +{
> > > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > > +     unsigned long *pll_dr = (unsigned long *)MCF_PLL_DR;
> > > +     u32 fvco, fsys, fesdhc, temp;
> > > +     const int sdclkfs[] = {2, 4, 8, 16, 32, 64, 128, 256};
> > > +     int delta, old_delta = clock;
> > > +     int i, q, ri, rq;
> > > +
> > > +     if (clock == 0) {
> > > +             host->mmc->actual_clock = 0;
> > > +             return;
> > > +     }
> > > +
> > > +     /*
> > > +      * ColdFire eSDHC clock.s
> > > +      *
> > > +      * pll -+-> / outdiv1 --> fsys
> > > +      *      +-> / outdiv3 --> eSDHC clock ---> / SDCCLKFS / DVS
> > > +      *
> > > +      * mcf5441x datasheet says:
> > > +      * (8.1.2) eSDHC should be 40 MHz max
> > > +      * (25.3.9) eSDHC input is, as example, 96 Mhz ...
> > > +      * (25.3.9) sd pin clock must never exceed 25Mhz
> > > +      *
> > > +      * fvco = fsys * outdvi1 + 1
> > > +      * fshdc = fvco / outdiv3 + 1
> > > +      */
> > > +     temp = readl(pll_dr);
> > > +     fsys = pltfm_host->clock;
> > > +     fvco = fsys * ((temp & 0x1f) + 1);
> > > +     fesdhc = fvco / (((temp >> 10) & 0x1f) + 1);
> > > +
> > > +     for (i = 0; i < 8; ++i) {
> > > +             int result = fesdhc / sdclkfs[i];
> > > +
> > > +             for (q = 1; q < 17; ++q) {
> > > +                     int finale = result / q;
> > > +
> > > +                     delta = abs(clock - finale);
> > > +
> > > +                     if (delta < old_delta) {
> > > +                             old_delta = delta;
> > > +                             ri = i;
> > > +                             rq = q;
> > > +                     }
> > > +             }
> > > +     }
> > > +
> > > +     /*
> > > +      * Apply divisors and re-enable all the clocks
> > > +      */
> > > +     temp = ((sdclkfs[ri] >> 1) << 8) | ((rq - 1) << 4) |
> > > +             (ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN);
> > > +     esdhc_clrset_be(host, 0x0000fff7, temp, SDHCI_CLOCK_CONTROL);
> > > +
> > > +     host->mmc->actual_clock = clock;
> > > +
> > > +     mdelay(1);
> > > +}
> > > +
> > > +static void esdhc_mcf_pltfm_set_bus_width(struct sdhci_host *host, int width)
> > > +{
> > > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > > +     struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> > > +
> > > +     switch (width) {
> > > +     case MMC_BUS_WIDTH_4:
> > > +             mcf_data->current_bus_width = ESDHC_CTRL_4BITBUS;
> > > +             break;
> > > +     default:
> > > +             mcf_data->current_bus_width = 0;
> > > +             break;
> > > +     }
> > > +
> > > +     esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
> > > +                     mcf_data->current_bus_width, SDHCI_HOST_CONTROL);
> > > +}
> > > +
> > > +static void esdhc_mcf_request_done(struct sdhci_host *host,
> > > +                                struct mmc_request *mrq)
> > > +{
> > > +     struct scatterlist *sg;
> > > +     u32 *buffer;
> > > +     int i;
> > > +
> > > +     if (!mrq->data || !mrq->data->bytes_xfered)
> > > +             goto exit_done;
> > > +
> > > +     if (mmc_get_dma_dir(mrq->data) != DMA_FROM_DEVICE)
> > > +             goto exit_done;
> > > +
> > > +     /*
> > > +      * On mcf5441x there is no hw sdma option/flag to select the dma
> > > +      * transfer endiannes. A swap after the transfer is needed.
> > > +      */
> > > +     for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i) {
> > > +             buffer = (u32 *)sg_virt(sg);
> > > +             esdhc_mcf_buffer_swap32(buffer, sg->length);
> > > +     }
> > > +
> > > +exit_done:
> > > +     mmc_request_done(host->mmc, mrq);
> > > +}
> > > +
> > > +static void esdhc_mcf_copy_to_bounce_buffer(struct sdhci_host *host,
> > > +                                         struct mmc_data *data)
> > > +{
> > > +     unsigned int length = data->blksz * data->blocks;
> > > +
> > > +     sg_copy_to_buffer(data->sg, data->sg_len,
> > > +                       host->bounce_buffer, length);
> > > +
> > > +     esdhc_mcf_buffer_swap32((u32 *)host->bounce_buffer,
> > > +                             data->blksz * data->blocks);
> >
> > data->blksz * data->blocks is already calculated as 'length'
> >
> > > +}
> > > +
> > > +static struct sdhci_ops sdhci_esdhc_ops = {
> > > +     .reset = esdhc_mcf_reset,
> > > +     .set_clock = esdhc_mcf_pltfm_set_clock,
> > > +     .get_max_clock = esdhc_mcf_pltfm_get_max_clock,
> > > +     .get_min_clock = esdhc_mcf_pltfm_get_min_clock,
> > > +     .set_bus_width = esdhc_mcf_pltfm_set_bus_width,
> > > +     .get_max_timeout_count = esdhc_mcf_get_max_timeout_count,
> > > +     .set_timeout = esdhc_mcf_set_timeout,
> > > +     .write_b = esdhc_mcf_writeb_be,
> > > +     .write_w = esdhc_mcf_writew_be,
> > > +     .write_l = esdhc_mcf_writel_be,
> > > +     .read_b = esdhc_mcf_readb_be,
> > > +     .read_w = esdhc_mcf_readw_be,
> > > +     .read_l = esdhc_mcf_readl_be,
> > > +     .copy_to_bounce_buffer = esdhc_mcf_copy_to_bounce_buffer,
> > > +     .request_done = esdhc_mcf_request_done,
> > > +};
> > > +
> > > +static const struct sdhci_pltfm_data sdhci_esdhc_mcf_pdata = {
> > > +     .ops = &sdhci_esdhc_ops,
> > > +     .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_FORCE_DMA,
> > > +              /*
> > > +               * Mandatory quirk,
> > > +               * controller does not support cmd23,
> > > +               * without, on > 8G cards cmd23 is used, and
> > > +               * driver times out.
> > > +               */
> > > +               SDHCI_QUIRK2_HOST_NO_CMD23,
> > > +};
> > > +
> > > +static int esdhc_mcf_plat_init(struct sdhci_host *host,
> > > +                            struct pltfm_mcf_data *mcf_data)
> > > +{
> > > +     struct mcf_esdhc_platform_data *plat_data;
> > > +
> > > +     if (!host->mmc->parent->platform_data) {
> > > +             dev_err(mmc_dev(host->mmc), "no platform data!\n");
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     plat_data = (struct mcf_esdhc_platform_data *)
> > > +                     host->mmc->parent->platform_data;
> > > +
> > > +     /* Card_detect */
> > > +     switch (plat_data->cd_type) {
> > > +     default:
> > > +     case ESDHC_CD_CONTROLLER:
> > > +             /* We have a working card_detect back */
> > > +             host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
> > > +             break;
> > > +     case ESDHC_CD_PERMANENT:
> > > +             host->mmc->caps |= MMC_CAP_NONREMOVABLE;
> > > +             break;
> > > +     case ESDHC_CD_NONE:
> > > +             break;
> > > +     }
> > > +
> > > +     switch (plat_data->max_bus_width) {
> > > +     case 4:
> > > +             host->mmc->caps |= MMC_CAP_4_BIT_DATA;
> > > +             break;
> > > +     case 1:
> > > +     default:
> > > +             host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
> > > +             break;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int sdhci_esdhc_mcf_probe(struct platform_device *pdev)
> > > +{
> > > +     struct sdhci_host *host;
> > > +     struct sdhci_pltfm_host *pltfm_host;
> > > +     struct pltfm_mcf_data *mcf_data;
> > > +     int err;
> > > +
> > > +     host = sdhci_pltfm_init(pdev, &sdhci_esdhc_mcf_pdata,
> > > +                             sizeof(*mcf_data));
> > > +
> > > +     if (IS_ERR(host))
> > > +             return PTR_ERR(host);
> > > +
> > > +     pltfm_host = sdhci_priv(host);
> > > +     mcf_data = sdhci_pltfm_priv(pltfm_host);
> > > +
> > > +     host->sdma_boundary = 0;
> > > +
> > > +     host->flags |= SDHCI_AUTO_CMD12;
> > > +
> > > +     mcf_data->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
> > > +     if (IS_ERR(mcf_data->clk_ipg)) {
> > > +             err = PTR_ERR(mcf_data->clk_ipg);
> > > +             goto err_exit;
> > > +     }
> > > +
> > > +     mcf_data->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
> > > +     if (IS_ERR(mcf_data->clk_ahb)) {
> > > +             err = PTR_ERR(mcf_data->clk_ahb);
> > > +             goto err_exit;
> > > +     }
> > > +
> > > +     mcf_data->clk_per = devm_clk_get(&pdev->dev, "per");
> > > +     if (IS_ERR(mcf_data->clk_per)) {
> > > +             err = PTR_ERR(mcf_data->clk_per);
> > > +             goto err_exit;
> > > +     }
> > > +
> > > +     pltfm_host->clk = mcf_data->clk_per;
> > > +     pltfm_host->clock = clk_get_rate(pltfm_host->clk);
> > > +     err = clk_prepare_enable(mcf_data->clk_per);
> > > +     if (err)
> > > +             goto err_exit;
> > > +
> > > +     err = clk_prepare_enable(mcf_data->clk_ipg);
> > > +     if (err)
> > > +             goto unprep_per;
> > > +
> > > +     err = clk_prepare_enable(mcf_data->clk_ahb);
> > > +     if (err)
> > > +             goto unprep_ipg;
> > > +
> > > +     err = esdhc_mcf_plat_init(host, mcf_data);
> > > +     if (err)
> > > +             goto unprep_ahb;
> > > +
> > > +     err = sdhci_setup_host(host);
> > > +     if (err)
> > > +             goto unprep_ahb;
> > > +
> > > +     if (!host->bounce_buffer) {
> > > +             dev_err(&pdev->dev, "bounce buffer not allocated");
> > > +             err = -ENOMEM;
> > > +             goto cleanup;
> > > +     }
> > > +
> > > +     err = __sdhci_add_host(host);
> > > +     if (err)
> > > +             goto cleanup;
> > > +
> > > +     return 0;
> > > +
> > > +cleanup:
> > > +     sdhci_cleanup_host(host);
> > > +unprep_ahb:
> > > +     clk_disable_unprepare(mcf_data->clk_ahb);
> > > +unprep_ipg:
> > > +     clk_disable_unprepare(mcf_data->clk_ipg);
> > > +unprep_per:
> > > +     clk_disable_unprepare(mcf_data->clk_per);
> > > +err_exit:
> > > +     sdhci_pltfm_free(pdev);
> > > +
> > > +     return err;
> > > +}
> > > +
> > > +static int sdhci_esdhc_mcf_remove(struct platform_device *pdev)
> > > +{
> > > +     struct sdhci_host *host = platform_get_drvdata(pdev);
> > > +     struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> > > +     struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
> > > +
> > > +     sdhci_remove_host(host, 0);
> > > +
> > > +     clk_disable_unprepare(mcf_data->clk_ipg);
> > > +     clk_disable_unprepare(mcf_data->clk_ahb);
> > > +     clk_disable_unprepare(mcf_data->clk_per);
> > > +
> > > +     sdhci_pltfm_free(pdev);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static struct platform_driver sdhci_esdhc_mcf_driver = {
> > > +     .driver = {
> > > +             .name = "sdhci-esdhc-mcf",
> > > +     },
> > > +     .probe = sdhci_esdhc_mcf_probe,
> > > +     .remove = sdhci_esdhc_mcf_remove,
> > > +};
> > > +
> > > +module_platform_driver(sdhci_esdhc_mcf_driver);
> > > +
> > > +MODULE_DESCRIPTION("SDHCI driver for Freescale ColdFire eSDHC");
> > > +MODULE_AUTHOR("Angelo Dureghello <angelo.dureghello@timesys.com>");
> > > +MODULE_LICENSE("GPL v2");
> > >
> >
>
>
> --
> Angelo Dureghello
> Timesys
> e. angelo.dureghello@timesys.com

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2020-05-18  8:03 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-05-15 22:27 [PATCH v5 1/3] m68k: mcf5441x: add support for esdhc mmc controller Angelo Dureghello
2020-05-15 22:27 ` [PATCH v5 2/3] mmc: sdhci: add quirks for be to le byte swapping Angelo Dureghello
2020-05-16 18:06   ` Adrian Hunter
2020-05-15 22:27 ` [PATCH v5 3/3] mmc: host: add Coldfire esdhc support Angelo Dureghello
2020-05-16 18:09   ` Adrian Hunter
2020-05-18  8:00     ` Angelo Dureghello
2020-05-18  8:02       ` Ulf Hansson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).