svn commit: r365054 - in head/sys: conf dev/sdhci

Marcin Wojtas mw at FreeBSD.org
Tue Sep 1 16:17:22 UTC 2020


Author: mw
Date: Tue Sep  1 16:17:21 2020
New Revision: 365054
URL: https://svnweb.freebsd.org/changeset/base/365054

Log:
  Introduce the SDHCI driver for NXP QorIQ Layerscape SoCs
  
  Implement support for an eSDHC controller found in NXP QorIQ Layerscape SoCs.
  
  This driver has been tested with NXP LS1046A and LX2160A (Honeycomb board),
  which is incompatible with the existing sdhci_fsl driver (aiming at older
  chips from this family). As such, it is not intended as replacement for
  the old driver, but rather serves as an improved alternative for SoCs that
  support it.
  It comes with support for both PIO and Single DMA modes and samples the
  clock from the extres clk API.
  
  Submitted by: Artur Rojek <ar at semihalf.com>
  Reviewed by: manu, mmel, kibab
  Obtained from: Semihalf
  Sponsored by: Alstom Group
  Differential Revision: https://reviews.freebsd.org/D26153

Added:
  head/sys/dev/sdhci/sdhci_fsl_fdt.c   (contents, props changed)
Modified:
  head/sys/conf/files

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files	Tue Sep  1 16:13:09 2020	(r365053)
+++ head/sys/conf/files	Tue Sep  1 16:17:21 2020	(r365054)
@@ -3058,6 +3058,7 @@ dev/scc/scc_dev_z8530.c		optional scc
 dev/sdhci/sdhci.c		optional sdhci
 dev/sdhci/sdhci_fdt.c		optional sdhci fdt
 dev/sdhci/sdhci_fdt_gpio.c	optional sdhci fdt gpio
+dev/sdhci/sdhci_fsl_fdt.c	optional sdhci fdt gpio
 dev/sdhci/sdhci_if.m		optional sdhci
 dev/sdhci/sdhci_acpi.c		optional sdhci acpi
 dev/sdhci/sdhci_pci.c		optional sdhci pci

Added: head/sys/dev/sdhci/sdhci_fsl_fdt.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/dev/sdhci/sdhci_fsl_fdt.c	Tue Sep  1 16:17:21 2020	(r365054)
@@ -0,0 +1,680 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Alstom Group.
+ * Copyright (c) 2020 Semihalf.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* eSDHC controller driver for NXP QorIQ Layerscape SoCs. */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/sysctl.h>
+#include <sys/taskqueue.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/mmc/bridge.h>
+#include <dev/mmc/mmcbrvar.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/sdhci/sdhci.h>
+#include <dev/sdhci/sdhci_fdt_gpio.h>
+
+#include "mmcbr_if.h"
+#include "sdhci_if.h"
+
+#define	RD4	(sc->read)
+#define	WR4	(sc->write)
+
+#define	SDHCI_FSL_PRES_STATE		0x24
+#define	SDHCI_FSL_PRES_SDSTB		(1 << 3)
+#define	SDHCI_FSL_PRES_COMPAT_MASK	0x000f0f07
+
+#define	SDHCI_FSL_PROT_CTRL		0x28
+#define	SDHCI_FSL_PROT_CTRL_WIDTH_1BIT	(0 << 1)
+#define	SDHCI_FSL_PROT_CTRL_WIDTH_4BIT	(1 << 1)
+#define	SDHCI_FSL_PROT_CTRL_WIDTH_8BIT	(2 << 1)
+#define	SDHCI_FSL_PROT_CTRL_WIDTH_MASK	(3 << 1)
+#define	SDHCI_FSL_PROT_CTRL_BYTE_SWAP	(0 << 4)
+#define	SDHCI_FSL_PROT_CTRL_BYTE_NATIVE	(2 << 4)
+#define	SDHCI_FSL_PROT_CTRL_BYTE_MASK	(3 << 4)
+#define	SDHCI_FSL_PROT_CTRL_DMA_MASK	(3 << 8)
+
+#define	SDHCI_FSL_SYS_CTRL		0x2c
+#define	SDHCI_FSL_CLK_IPGEN		(1 << 0)
+#define	SDHCI_FSL_CLK_SDCLKEN		(1 << 3)
+#define	SDHCI_FSL_CLK_DIVIDER_MASK	0x000000f0
+#define	SDHCI_FSL_CLK_DIVIDER_SHIFT	4
+#define	SDHCI_FSL_CLK_PRESCALE_MASK	0x0000ff00
+#define	SDHCI_FSL_CLK_PRESCALE_SHIFT	8
+
+#define	SDHCI_FSL_WTMK_LVL		0x44
+#define	SDHCI_FSL_WTMK_RD_512B		(0 << 0)
+#define	SDHCI_FSL_WTMK_WR_512B		(0 << 15)
+
+#define	SDHCI_FSL_HOST_VERSION		0xfc
+#define	SDHCI_FSL_CAPABILITIES2		0x114
+
+#define	SDHCI_FSL_ESDHC_CTRL		0x40c
+#define	SDHCI_FSL_ESDHC_CTRL_SNOOP	(1 << 6)
+#define	SDHCI_FSL_ESDHC_CTRL_CLK_DIV2	(1 << 19)
+
+struct sdhci_fsl_fdt_softc {
+	device_t				dev;
+	const struct sdhci_fsl_fdt_soc_data	*soc_data;
+	struct resource				*mem_res;
+	struct resource				*irq_res;
+	void					*irq_cookie;
+	uint32_t				baseclk_hz;
+	struct sdhci_fdt_gpio			*gpio;
+	struct sdhci_slot			slot;
+	bool					slot_init_done;
+	uint32_t				cmd_and_mode;
+	uint16_t				sdclk_bits;
+
+	uint32_t (* read)(struct sdhci_fsl_fdt_softc *, bus_size_t);
+	void (* write)(struct sdhci_fsl_fdt_softc *, bus_size_t, uint32_t);
+};
+
+struct sdhci_fsl_fdt_soc_data {
+	int quirks;
+};
+
+static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_ls1046a_soc_data = {
+	.quirks = SDHCI_QUIRK_DONT_SET_HISPD_BIT | SDHCI_QUIRK_BROKEN_AUTO_STOP
+};
+
+static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_gen_data = {
+	.quirks = 0,
+};
+
+static const struct ofw_compat_data sdhci_fsl_fdt_compat_data[] = {
+	{"fsl,ls1046a-esdhc",	(uintptr_t)&sdhci_fsl_fdt_ls1046a_soc_data},
+	{"fsl,esdhc",		(uintptr_t)&sdhci_fsl_fdt_gen_data},
+	{NULL,			0}
+};
+
+static uint32_t
+read_be(struct sdhci_fsl_fdt_softc *sc, bus_size_t off)
+{
+
+	return (be32toh(bus_read_4(sc->mem_res, off)));
+}
+
+static void
+write_be(struct sdhci_fsl_fdt_softc *sc, bus_size_t off, uint32_t val)
+{
+
+	bus_write_4(sc->mem_res, off, htobe32(val));
+}
+
+static uint32_t
+read_le(struct sdhci_fsl_fdt_softc *sc, bus_size_t off)
+{
+
+	return (bus_read_4(sc->mem_res, off));
+}
+
+static void
+write_le(struct sdhci_fsl_fdt_softc *sc, bus_size_t off, uint32_t val)
+{
+
+	bus_write_4(sc->mem_res, off, val);
+}
+
+
+static uint16_t
+sdhci_fsl_fdt_get_clock(struct sdhci_fsl_fdt_softc *sc)
+{
+	uint16_t val;
+
+	val = sc->sdclk_bits | SDHCI_CLOCK_INT_EN;
+	if (RD4(sc, SDHCI_FSL_PRES_STATE) & SDHCI_FSL_PRES_SDSTB)
+		val |= SDHCI_CLOCK_INT_STABLE;
+	if (RD4(sc, SDHCI_FSL_SYS_CTRL) & SDHCI_FSL_CLK_SDCLKEN)
+		val |= SDHCI_CLOCK_CARD_EN;
+
+	return (val);
+}
+
+static void
+fsl_sdhc_fdt_set_clock(struct sdhci_fsl_fdt_softc *sc, uint16_t val)
+{
+	uint32_t div, freq, prescale, val32;
+
+	sc->sdclk_bits = val & SDHCI_DIVIDERS_MASK;
+	val32 = RD4(sc, SDHCI_CLOCK_CONTROL);
+
+	if ((val & SDHCI_CLOCK_CARD_EN) == 0) {
+		WR4(sc, SDHCI_CLOCK_CONTROL, val32 & ~SDHCI_FSL_CLK_SDCLKEN);
+		return;
+	}
+
+	div = ((val >> SDHCI_DIVIDER_SHIFT) & SDHCI_DIVIDER_MASK) |
+	    ((val >> SDHCI_DIVIDER_HI_SHIFT) & SDHCI_DIVIDER_HI_MASK) <<
+	    SDHCI_DIVIDER_MASK_LEN;
+	if (div == 0)
+		freq = sc->baseclk_hz;
+	else
+		freq = sc->baseclk_hz / (2 * div);
+
+	for (prescale = 2; freq < sc->baseclk_hz / (prescale * 16); )
+		prescale <<= 1;
+	for (div = 1; freq < sc->baseclk_hz / (prescale * div); )
+		++div;
+
+#ifdef DEBUG
+	device_printf(sc->dev,
+	    "Desired SD/MMC freq: %d, actual: %d; base %d prescale %d divisor %d\n",
+	    freq, sc->baseclk_hz / (prescale * div),
+	    sc->baseclk_hz, prescale, div);
+#endif
+
+	prescale >>= 1;
+	div -= 1;
+
+	val32 &= ~(SDHCI_FSL_CLK_DIVIDER_MASK | SDHCI_FSL_CLK_PRESCALE_MASK);
+	val32 |= div << SDHCI_FSL_CLK_DIVIDER_SHIFT;
+	val32 |= prescale << SDHCI_FSL_CLK_PRESCALE_SHIFT;
+	val32 |= SDHCI_FSL_CLK_IPGEN | SDHCI_FSL_CLK_SDCLKEN;
+	WR4(sc, SDHCI_CLOCK_CONTROL, val32);
+}
+
+static uint8_t
+sdhci_fsl_fdt_read_1(device_t dev, struct sdhci_slot *slot, bus_size_t off)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+	uint32_t wrk32, val32;
+
+	sc = device_get_softc(dev);
+
+	switch (off) {
+	case SDHCI_HOST_CONTROL:
+		wrk32 = RD4(sc, SDHCI_FSL_PROT_CTRL);
+		val32 = wrk32 & (SDHCI_CTRL_LED | SDHCI_CTRL_CARD_DET |
+		    SDHCI_CTRL_FORCE_CARD);
+		if (wrk32 & SDHCI_FSL_PROT_CTRL_WIDTH_4BIT)
+			val32 |= SDHCI_CTRL_4BITBUS;
+		else if (wrk32 & SDHCI_FSL_PROT_CTRL_WIDTH_8BIT)
+			val32 |= SDHCI_CTRL_8BITBUS;
+		return (val32);
+	case SDHCI_POWER_CONTROL:
+		return (SDHCI_POWER_ON | SDHCI_POWER_300);
+	default:
+		break;
+	}
+
+	return ((RD4(sc, off & ~3) >> (off & 3) * 8) & UINT8_MAX);
+}
+
+static uint16_t
+sdhci_fsl_fdt_read_2(device_t dev, struct sdhci_slot *slot, bus_size_t off)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+	uint32_t val32;
+
+	sc = device_get_softc(dev);
+
+	switch (off) {
+	case SDHCI_CLOCK_CONTROL:
+		return (sdhci_fsl_fdt_get_clock(sc));
+	case SDHCI_HOST_VERSION:
+		return (RD4(sc, SDHCI_FSL_HOST_VERSION) & UINT16_MAX);
+	case SDHCI_TRANSFER_MODE:
+		return (sc->cmd_and_mode & UINT16_MAX);
+	case SDHCI_COMMAND_FLAGS:
+		return (sc->cmd_and_mode >> 16);
+	case SDHCI_SLOT_INT_STATUS:
+	/*
+	 * eSDHC hardware manages only a single slot.
+	 * Synthesize a slot interrupt status register for slot 1 below.
+	 */
+		val32 = RD4(sc, SDHCI_INT_STATUS);
+		val32 &= RD4(sc, SDHCI_SIGNAL_ENABLE);
+		return (!!val32);
+	default:
+		return ((RD4(sc, off & ~3) >> (off & 3) * 8) & UINT16_MAX);
+	}
+}
+
+static uint32_t
+sdhci_fsl_fdt_read_4(device_t dev, struct sdhci_slot *slot, bus_size_t off)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+	uint32_t wrk32, val32;
+
+	sc = device_get_softc(dev);
+
+	if (off == SDHCI_BUFFER)
+		return (bus_read_4(sc->mem_res, off));
+	if (off == SDHCI_CAPABILITIES2)
+		off = SDHCI_FSL_CAPABILITIES2;
+
+	val32 = RD4(sc, off);
+
+	switch (off) {
+	case SDHCI_CAPABILITIES:
+		val32 &= ~(SDHCI_CAN_DO_SUSPEND | SDHCI_CAN_VDD_180);
+		break;
+	case SDHCI_PRESENT_STATE:
+		wrk32 = val32;
+		val32 &= SDHCI_FSL_PRES_COMPAT_MASK;
+		val32 |= (wrk32 >> 4) & SDHCI_STATE_DAT_MASK;
+		val32 |= (wrk32 << 1) & SDHCI_STATE_CMD;
+		break;
+	default:
+		break;
+	}
+
+	return (val32);
+}
+
+static void
+sdhci_fsl_fdt_read_multi_4(device_t dev, struct sdhci_slot *slot, bus_size_t off,
+    uint32_t *data, bus_size_t count)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+
+	sc = device_get_softc(dev);
+	bus_read_multi_4(sc->mem_res, off, data, count);
+}
+
+static void
+sdhci_fsl_fdt_write_1(device_t dev, struct sdhci_slot *slot, bus_size_t off,
+    uint8_t val)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+	uint32_t val32;
+
+	sc = device_get_softc(dev);
+
+	switch (off) {
+	case SDHCI_HOST_CONTROL:
+		val32 = RD4(sc, SDHCI_FSL_PROT_CTRL);
+		val32 &= ~SDHCI_FSL_PROT_CTRL_WIDTH_MASK;
+		val32 |= (val & SDHCI_CTRL_LED);
+
+		if (val & SDHCI_CTRL_8BITBUS)
+			val32 |= SDHCI_FSL_PROT_CTRL_WIDTH_8BIT;
+		else
+			/* Bus width is 1-bit when this flag is not set. */
+			val32 |= (val & SDHCI_CTRL_4BITBUS);
+		/* Enable SDMA by masking out this field. */
+		val32 &= ~SDHCI_FSL_PROT_CTRL_DMA_MASK;
+		val32 &= ~(SDHCI_CTRL_CARD_DET | SDHCI_CTRL_FORCE_CARD);
+		val32 |= (val & (SDHCI_CTRL_CARD_DET |
+		    SDHCI_CTRL_FORCE_CARD));
+		WR4(sc, SDHCI_FSL_PROT_CTRL, val32);
+		return;
+	case SDHCI_POWER_CONTROL:
+		return;
+	case SDHCI_SOFTWARE_RESET:
+		val &= ~SDHCI_RESET_ALL;
+	/* FALLTHROUGH. */
+	default:
+		val32 = RD4(sc, off & ~3);
+		val32 &= ~(UINT8_MAX << (off & 3) * 8);
+		val32 |= (val << (off & 3) * 8);
+		WR4(sc, off & ~3, val32);
+		return;
+	}
+}
+
+static void
+sdhci_fsl_fdt_write_2(device_t dev, struct sdhci_slot *slot, bus_size_t off,
+    uint16_t val)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+	uint32_t val32;
+
+	sc = device_get_softc(dev);
+
+	switch (off) {
+	case SDHCI_CLOCK_CONTROL:
+		fsl_sdhc_fdt_set_clock(sc, val);
+		return;
+	/*
+	 * eSDHC hardware combines command and mode into a single
+	 * register. Cache it here, so that command isn't written
+	 * until after mode.
+	 */
+	case SDHCI_TRANSFER_MODE:
+		sc->cmd_and_mode = val;
+		return;
+	case SDHCI_COMMAND_FLAGS:
+		sc->cmd_and_mode =
+		    (sc->cmd_and_mode & UINT16_MAX) | (val << 16);
+		WR4(sc, SDHCI_TRANSFER_MODE, sc->cmd_and_mode);
+		sc->cmd_and_mode = 0;
+		return;
+	default:
+		val32 = RD4(sc, off & ~3);
+		val32 &= ~(UINT16_MAX << (off & 3) * 8);
+		val32 |= ((val & UINT16_MAX) << (off & 3) * 8);
+		WR4(sc, off & ~3, val32);
+		return;
+	}
+}
+
+static void
+sdhci_fsl_fdt_write_4(device_t dev, struct sdhci_slot *slot, bus_size_t off,
+    uint32_t val)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	switch (off) {
+	case SDHCI_BUFFER:
+		bus_write_4(sc->mem_res, off, val);
+		return;
+	/*
+	 * eSDHC hardware lacks support for the SDMA buffer boundary
+	 * feature and instead generates SDHCI_INT_DMA_END interrupts
+	 * after each completed DMA data transfer.
+	 * Since this duplicates the SDHCI_INT_DATA_END functionality,
+	 * mask out the unneeded SDHCI_INT_DMA_END interrupt.
+	 */
+	case SDHCI_INT_ENABLE:
+	case SDHCI_SIGNAL_ENABLE:
+		val &= ~SDHCI_INT_DMA_END;
+	/* FALLTHROUGH. */
+	default:
+		WR4(sc, off, val);
+		return;
+	}
+}
+
+static void
+sdhci_fsl_fdt_write_multi_4(device_t dev, struct sdhci_slot *slot,
+    bus_size_t off, uint32_t *data, bus_size_t count)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+
+	sc = device_get_softc(dev);
+	bus_write_multi_4(sc->mem_res, off, data, count);
+}
+
+static void
+sdhci_fsl_fdt_irq(void *arg)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+
+	sc = arg;
+	sdhci_generic_intr(&sc->slot);
+	return;
+}
+
+static int
+sdhci_fsl_fdt_get_ro(device_t bus, device_t child)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+
+	sc = device_get_softc(bus);
+	return (sdhci_fdt_gpio_get_readonly(sc->gpio));
+}
+
+static bool
+sdhci_fsl_fdt_get_card_present(device_t dev, struct sdhci_slot *slot)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+
+	sc = device_get_softc(dev);
+	return (sdhci_fdt_gpio_get_present(sc->gpio));
+}
+
+static int
+sdhci_fsl_fdt_attach(device_t dev)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+	uint32_t val, buf_order;
+	uintptr_t ocd_data;
+	uint64_t clk_hz;
+	phandle_t node;
+	int rid, ret;
+	clk_t clk;
+
+	node = ofw_bus_get_node(dev);
+	sc = device_get_softc(dev);
+	ocd_data = ofw_bus_search_compatible(dev,
+	    sdhci_fsl_fdt_compat_data)->ocd_data;
+	sc->soc_data = (struct sdhci_fsl_fdt_soc_data *)ocd_data;
+	sc->dev = dev;
+	sc->slot.quirks = sc->soc_data->quirks;
+
+	rid = 0;
+	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+	    RF_ACTIVE);
+	if (sc->mem_res == NULL) {
+		device_printf(dev,
+		    "Could not allocate resources for controller\n");
+		return (ENOMEM);
+	}
+
+	rid = 0;
+	sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+	    RF_ACTIVE);
+	if (sc->irq_res == NULL) {
+		device_printf(dev,
+		    "Could not allocate irq resources for controller\n");
+		ret = ENOMEM;
+		goto err_free_mem;
+	}
+
+	ret = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+	    NULL, sdhci_fsl_fdt_irq, sc, &sc->irq_cookie);
+	if (ret != 0) {
+		device_printf(dev, "Could not setup IRQ handler\n");
+		goto err_free_irq_res;
+	}
+
+	ret = clk_get_by_ofw_index(dev, node, 0, &clk);
+	if (ret != 0) {
+		device_printf(dev, "Parent clock not found\n");
+		goto err_free_irq;
+	}
+
+	ret = clk_get_freq(clk, &clk_hz);
+	if (ret != 0) {
+		device_printf(dev,
+		    "Could not get parent clock frequency\n");
+		goto err_free_irq;
+	}
+
+	sc->baseclk_hz = clk_hz / 2;
+
+	/* Figure out eSDHC block endianness before we touch any HW regs. */
+	if (OF_hasprop(node, "little-endian")) {
+		sc->read = read_le;
+		sc->write = write_le;
+		buf_order = SDHCI_FSL_PROT_CTRL_BYTE_NATIVE;
+	} else {
+		sc->read = read_be;
+		sc->write = write_be;
+		buf_order = SDHCI_FSL_PROT_CTRL_BYTE_SWAP;
+	}
+
+	/*
+	 * Setting this register affects byte order in SDHCI_BUFFER only.
+	 * If the eSDHC block is connected over a big-endian bus, the data
+	 * read from/written to the buffer will be already byte swapped.
+	 * In such a case, setting SDHCI_FSL_PROT_CTRL_BYTE_SWAP will convert
+	 * the byte order again, resulting in a native byte order.
+	 * The read/write callbacks accommodate for this behavior.
+	 */
+	val = RD4(sc, SDHCI_FSL_PROT_CTRL);
+	val &= ~SDHCI_FSL_PROT_CTRL_BYTE_MASK;
+	WR4(sc, SDHCI_FSL_PROT_CTRL, val | buf_order);
+
+	/*
+	 * Gate the SD clock and set its source to peripheral clock / 2.
+	 * The frequency in baseclk_hz is set to match this.
+	 */
+	val = RD4(sc, SDHCI_CLOCK_CONTROL);
+	WR4(sc, SDHCI_CLOCK_CONTROL, val & ~SDHCI_FSL_CLK_SDCLKEN);
+	val = RD4(sc, SDHCI_FSL_ESDHC_CTRL);
+	WR4(sc, SDHCI_FSL_ESDHC_CTRL, val | SDHCI_FSL_ESDHC_CTRL_CLK_DIV2);
+	sc->slot.max_clk = sc->baseclk_hz;
+	sc->gpio = sdhci_fdt_gpio_setup(dev, &sc->slot);
+
+	/*
+	 * Set the buffer watermark level to 128 words (512 bytes) for both
+	 * read and write. The hardware has a restriction that when the read or
+	 * write ready status is asserted, that means you can read exactly the
+	 * number of words set in the watermark register before you have to
+	 * re-check the status and potentially wait for more data. The main
+	 * sdhci driver provides no hook for doing status checking on less than
+	 * a full block boundary, so we set the watermark level to be a full
+	 * block. Reads and writes where the block size is less than the
+	 * watermark size will work correctly too, no need to change the
+	 * watermark for different size blocks. However, 128 is the maximum
+	 * allowed for the watermark, so PIO is limitted to 512 byte blocks.
+	 */
+	WR4(sc, SDHCI_FSL_WTMK_LVL, SDHCI_FSL_WTMK_WR_512B |
+	    SDHCI_FSL_WTMK_RD_512B);
+
+	ret = sdhci_init_slot(dev, &sc->slot, 0);
+	if (ret != 0)
+		goto err_free_gpio;
+	sc->slot_init_done = true;
+	sdhci_start_slot(&sc->slot);
+
+	return (bus_generic_attach(dev));
+
+err_free_gpio:
+	sdhci_fdt_gpio_teardown(sc->gpio);
+err_free_irq:
+	bus_teardown_intr(dev, sc->irq_res, sc->irq_cookie);
+err_free_irq_res:
+	bus_free_resource(dev, SYS_RES_IRQ, sc->irq_res);
+err_free_mem:
+	bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
+	return (ret);
+}
+
+static int
+sdhci_fsl_fdt_detach(device_t dev)
+{
+	struct sdhci_fsl_fdt_softc *sc;
+
+	sc = device_get_softc(dev);
+	if (sc->slot_init_done)
+		sdhci_cleanup_slot(&sc->slot);
+	if (sc->gpio != NULL)
+		sdhci_fdt_gpio_teardown(sc->gpio);
+	if (sc->irq_cookie != NULL)
+		bus_teardown_intr(dev, sc->irq_res, sc->irq_cookie);
+	if (sc->irq_res != NULL)
+		bus_free_resource(dev, SYS_RES_IRQ, sc->irq_res);
+	if (sc->mem_res != NULL)
+		bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res);
+	return (0);
+}
+
+static int
+sdhci_fsl_fdt_probe(device_t dev)
+{
+
+	if (!ofw_bus_status_okay(dev))
+		return (ENXIO);
+
+	if (!ofw_bus_search_compatible(dev,
+	   sdhci_fsl_fdt_compat_data)->ocd_data)
+		return (ENXIO);
+
+	device_set_desc(dev, "NXP QorIQ Layerscape eSDHC controller");
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+sdhci_fsl_fdt_read_ivar(device_t bus, device_t child, int which,
+    uintptr_t *result)
+{
+	struct sdhci_slot *slot = device_get_ivars(child);
+
+	if (which == MMCBR_IVAR_MAX_DATA && (slot->opt & SDHCI_HAVE_DMA)) {
+		/*
+		 * In the absence of SDMA buffer boundary functionality,
+		 * limit the maximum data length per read/write command
+		 * to bounce buffer size.
+		 */
+		*result = howmany(slot->sdma_bbufsz, 512);
+		return (0);
+	}
+	return (sdhci_generic_read_ivar(bus, child, which, result));
+}
+
+static const device_method_t sdhci_fsl_fdt_methods[] = {
+	/* Device interface. */
+	DEVMETHOD(device_probe,			sdhci_fsl_fdt_probe),
+	DEVMETHOD(device_attach,		sdhci_fsl_fdt_attach),
+	DEVMETHOD(device_detach,		sdhci_fsl_fdt_detach),
+
+	/* Bus interface. */
+	DEVMETHOD(bus_read_ivar,		sdhci_fsl_fdt_read_ivar),
+	DEVMETHOD(bus_write_ivar,		sdhci_generic_write_ivar),
+
+	/* MMC bridge interface. */
+	DEVMETHOD(mmcbr_update_ios,		sdhci_generic_update_ios),
+	DEVMETHOD(mmcbr_request,		sdhci_generic_request),
+	DEVMETHOD(mmcbr_get_ro,			sdhci_fsl_fdt_get_ro),
+	DEVMETHOD(mmcbr_acquire_host,		sdhci_generic_acquire_host),
+	DEVMETHOD(mmcbr_release_host,		sdhci_generic_release_host),
+
+	/* SDHCI accessors. */
+	DEVMETHOD(sdhci_read_1,			sdhci_fsl_fdt_read_1),
+	DEVMETHOD(sdhci_read_2,			sdhci_fsl_fdt_read_2),
+	DEVMETHOD(sdhci_read_4,			sdhci_fsl_fdt_read_4),
+	DEVMETHOD(sdhci_read_multi_4,		sdhci_fsl_fdt_read_multi_4),
+	DEVMETHOD(sdhci_write_1,		sdhci_fsl_fdt_write_1),
+	DEVMETHOD(sdhci_write_2,		sdhci_fsl_fdt_write_2),
+	DEVMETHOD(sdhci_write_4,		sdhci_fsl_fdt_write_4),
+	DEVMETHOD(sdhci_write_multi_4,		sdhci_fsl_fdt_write_multi_4),
+	DEVMETHOD(sdhci_get_card_present,	sdhci_fsl_fdt_get_card_present),
+	DEVMETHOD_END
+};
+
+static devclass_t sdhci_fsl_fdt_devclass;
+static driver_t sdhci_fsl_fdt_driver = {
+	"sdhci_fsl_fdt",
+	sdhci_fsl_fdt_methods,
+	sizeof(struct sdhci_fsl_fdt_softc),
+};
+
+DRIVER_MODULE(sdhci_fsl_fdt, simplebus, sdhci_fsl_fdt_driver,
+    sdhci_fsl_fdt_devclass, NULL, NULL);
+SDHCI_DEPEND(sdhci_fsl_fdt);
+
+#ifndef MMCCAM
+MMC_DECLARE_BRIDGE(sdhci_fsl_fdt);
+#endif


More information about the svn-src-all mailing list