summaryrefslogtreecommitdiffstats
path: root/sys/arm/freescale
diff options
context:
space:
mode:
authorian <ian@FreeBSD.org>2014-05-15 22:50:06 +0000
committerian <ian@FreeBSD.org>2014-05-15 22:50:06 +0000
commit56c3d6e841618384b80deb80cf39041b759a53da (patch)
tree96866ad37d20b44cec8beddfcde3eb11f1575390 /sys/arm/freescale
parentdbea31deb0f37bf5ff55e4cd39230b347deba1c1 (diff)
downloadFreeBSD-src-56c3d6e841618384b80deb80cf39041b759a53da.zip
FreeBSD-src-56c3d6e841618384b80deb80cf39041b759a53da.tar.gz
MFC r261982, r261987, r262123, r262244, r262278, r262280, r262353, r262354,
r262355, r262419, Add Vybrid driver for Synchronous Audio Interface (SAI). Decrease SAI buffer size. Handle eDMA interrupt on running channel only. Give the physmem fdt helper routines static linkage since no global definition of them is provided anywhere. Add imx6 early printf support, wrapped in #if 0 because it's rarely needed. Add basic cpu frequency control and temperature monitoring to imx6_anatop. Add the FREEBSD_BOOT_LOADER option so that a loaded DTB passed in from ubldr will actually get used. Create a generic IMX6 kernel config, then fix it to have an ident line. Don't force imx6 bootverbose on anymore, it can be set from ubldr now.
Diffstat (limited to 'sys/arm/freescale')
-rw-r--r--sys/arm/freescale/imx/imx6_anatop.c399
-rw-r--r--sys/arm/freescale/imx/imx6_anatopreg.h63
-rw-r--r--sys/arm/freescale/imx/imx6_anatopvar.h2
-rw-r--r--sys/arm/freescale/imx/imx6_machdep.c27
-rw-r--r--sys/arm/freescale/vybrid/files.vybrid1
-rw-r--r--sys/arm/freescale/vybrid/vf_sai.c805
6 files changed, 1268 insertions, 29 deletions
diff --git a/sys/arm/freescale/imx/imx6_anatop.c b/sys/arm/freescale/imx/imx6_anatop.c
index 893fb18..c393656 100644
--- a/sys/arm/freescale/imx/imx6_anatop.c
+++ b/sys/arm/freescale/imx/imx6_anatop.c
@@ -1,5 +1,6 @@
/*-
* Copyright (c) 2013 Ian Lepore <ian@freebsd.org>
+ * Copyright (c) 2014 Steven Lawrance <stl@koffein.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -29,6 +30,8 @@ __FBSDID("$FreeBSD$");
/*
* Analog PLL and power regulator driver for Freescale i.MX6 family of SoCs.
+ * Also, temperature montoring and cpu frequency control. It was Freescale who
+ * kitchen-sinked this device, not us. :)
*
* We don't really do anything with analog PLLs, but the registers for
* controlling them belong to the same block as the power regulator registers.
@@ -42,11 +45,19 @@ __FBSDID("$FreeBSD$");
* I have no idea where the "anatop" name comes from. It's in the standard DTS
* source describing i.MX6 SoCs, and in the linux and u-boot code which comes
* from Freescale, but it's not in the SoC manual.
+ *
+ * Note that temperature values throughout this code are handled in two types of
+ * units. Items with '_cnt' in the name use the hardware temperature count
+ * units (higher counts are lower temperatures). Items with '_val' in the name
+ * are deci-Celcius, which are converted to/from deci-Kelvins in the sysctl
+ * handlers (dK is the standard unit for temperature in sysctl).
*/
#include <sys/param.h>
#include <sys/systm.h>
+#include <sys/callout.h>
#include <sys/kernel.h>
+#include <sys/sysctl.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/rman.h>
@@ -56,68 +67,410 @@ __FBSDID("$FreeBSD$");
#include <machine/bus.h>
+#include <arm/freescale/fsl_ocotpreg.h>
+#include <arm/freescale/fsl_ocotpvar.h>
#include <arm/freescale/imx/imx6_anatopreg.h>
#include <arm/freescale/imx/imx6_anatopvar.h>
+static struct resource_spec imx6_anatop_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE },
+ { -1, 0 }
+};
+#define MEMRES 0
+#define IRQRES 1
+
struct imx6_anatop_softc {
device_t dev;
- struct resource *mem_res;
+ struct resource *res[2];
+ uint32_t cpu_curhz;
+ uint32_t cpu_curmhz;
+ uint32_t cpu_minhz;
+ uint32_t cpu_maxhz;
+ uint32_t refosc_hz;
+ void *temp_intrhand;
+ uint32_t temp_high_val;
+ uint32_t temp_high_cnt;
+ uint32_t temp_last_cnt;
+ uint32_t temp_room_cnt;
+ struct callout temp_throttle_callout;
+ sbintime_t temp_throttle_delay;
+ uint32_t temp_throttle_reset_cnt;
+ uint32_t temp_throttle_trigger_cnt;
+ uint32_t temp_throttle_val;
};
static struct imx6_anatop_softc *imx6_anatop_sc;
+/*
+ * Table of CPU max frequencies. This is indexed by the max frequency value
+ * (0-3) from the ocotp CFG3 register.
+ */
+static uint32_t imx6_cpu_maxhz_tab[] = {
+ 792000000, 852000000, 996000000, 1200000000
+};
+
+#define TZ_ZEROC 2732 /* deci-Kelvin <-> deci-Celcius offset. */
+
uint32_t
imx6_anatop_read_4(bus_size_t offset)
{
- return (bus_read_4(imx6_anatop_sc->mem_res, offset));
+ KASSERT(imx6_anatop_sc != NULL, ("imx6_anatop_read_4 sc NULL"));
+
+ return (bus_read_4(imx6_anatop_sc->res[MEMRES], offset));
}
void
imx6_anatop_write_4(bus_size_t offset, uint32_t value)
{
- bus_write_4(imx6_anatop_sc->mem_res, offset, value);
+ KASSERT(imx6_anatop_sc != NULL, ("imx6_anatop_write_4 sc NULL"));
+
+ bus_write_4(imx6_anatop_sc->res[MEMRES], offset, value);
+}
+
+static inline uint32_t
+cpufreq_hz_from_div(struct imx6_anatop_softc *sc, uint32_t div)
+{
+
+ return (sc->refosc_hz * (div / 2));
+}
+
+static inline uint32_t
+cpufreq_hz_to_div(struct imx6_anatop_softc *sc, uint32_t cpu_hz)
+{
+
+ return (cpu_hz / (sc->refosc_hz / 2));
+}
+
+static inline uint32_t
+cpufreq_actual_hz(struct imx6_anatop_softc *sc, uint32_t cpu_hz)
+{
+
+ return (cpufreq_hz_from_div(sc, cpufreq_hz_to_div(sc, cpu_hz)));
+}
+
+static void
+cpufreq_set_clock(struct imx6_anatop_softc * sc, uint32_t cpu_newhz)
+{
+ uint32_t div, timeout, wrk32;
+ const uint32_t mindiv = 54;
+ const uint32_t maxdiv = 108;
+
+ /*
+ * Clip the requested frequency to the configured max, then clip the
+ * resulting divisor to the documented min/max values.
+ */
+ cpu_newhz = min(cpu_newhz, sc->cpu_maxhz);
+ div = cpufreq_hz_to_div(sc, cpu_newhz);
+ if (div < mindiv)
+ div = mindiv;
+ else if (div > maxdiv)
+ div = maxdiv;
+ sc->cpu_curhz = cpufreq_hz_from_div(sc, div);
+ sc->cpu_curmhz = sc->cpu_curhz / 1000000;
+
+ /*
+ * I can't find a documented procedure for changing the ARM PLL divisor,
+ * but some trial and error came up with this:
+ * - Set the bypass clock source to REF_CLK_24M (source #0).
+ * - Set the PLL into bypass mode; cpu should now be running at 24mhz.
+ * - Change the divisor.
+ * - Wait for the LOCK bit to come on; it takes ~50 loop iterations.
+ * - Turn off bypass mode; cpu should now be running at cpu_newhz.
+ */
+ imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM_CLR,
+ IMX6_ANALOG_CCM_PLL_ARM_CLK_SRC_MASK);
+ imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM_SET,
+ IMX6_ANALOG_CCM_PLL_ARM_BYPASS);
+
+ wrk32 = imx6_anatop_read_4(IMX6_ANALOG_CCM_PLL_ARM);
+ wrk32 &= ~IMX6_ANALOG_CCM_PLL_ARM_DIV_MASK;
+ wrk32 |= div;
+ imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM, wrk32);
+
+ timeout = 10000;
+ while ((imx6_anatop_read_4(IMX6_ANALOG_CCM_PLL_ARM) &
+ IMX6_ANALOG_CCM_PLL_ARM_LOCK) == 0)
+ if (--timeout == 0)
+ panic("imx6_set_cpu_clock(): PLL never locked");
+
+ imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM_CLR,
+ IMX6_ANALOG_CCM_PLL_ARM_BYPASS);
+}
+
+static void
+cpufreq_initialize(struct imx6_anatop_softc *sc)
+{
+ uint32_t cfg3speed;
+ struct sysctl_ctx_list *ctx;
+
+ ctx = device_get_sysctl_ctx(sc->dev);
+ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
+ OID_AUTO, "cpu_mhz", CTLFLAG_RD, &sc->cpu_curmhz, 0,
+ "CPU frequency in MHz");
+
+ /*
+ * XXX 24mhz shouldn't be hard-coded, should get this from imx6_ccm
+ * (even though in the real world it will always be 24mhz). Oh wait a
+ * sec, I never wrote imx6_ccm.
+ */
+ sc->refosc_hz = 24000000;
+
+ /*
+ * Get the maximum speed this cpu can be set to. The values in the
+ * OCOTP CFG3 register are not documented in the reference manual.
+ * The following info was in an archived email found via web search:
+ * - 2b'11: 1200000000Hz;
+ * - 2b'10: 996000000Hz;
+ * - 2b'01: 852000000Hz; -- i.MX6Q Only, exclusive with 996MHz.
+ * - 2b'00: 792000000Hz;
+ */
+ cfg3speed = (fsl_ocotp_read_4(FSL_OCOTP_CFG3) &
+ FSL_OCOTP_CFG3_SPEED_MASK) >> FSL_OCOTP_CFG3_SPEED_SHIFT;
+
+ sc->cpu_minhz = cpufreq_actual_hz(sc, imx6_cpu_maxhz_tab[0]);
+ sc->cpu_maxhz = cpufreq_actual_hz(sc, imx6_cpu_maxhz_tab[cfg3speed]);
+
+ /*
+ * Set the CPU to maximum speed.
+ *
+ * We won't have thermal throttling until interrupts are enabled, but we
+ * want to run at full speed through all the device init stuff. This
+ * basically assumes that a single core can't overheat before interrupts
+ * are enabled; empirical testing shows that to be a safe assumption.
+ */
+ cpufreq_set_clock(sc, sc->cpu_maxhz);
+ device_printf(sc->dev, "CPU frequency %uMHz\n", sc->cpu_curmhz);
+}
+
+static inline uint32_t
+temp_from_count(struct imx6_anatop_softc *sc, uint32_t count)
+{
+
+ return (((sc->temp_high_val - (count - sc->temp_high_cnt) *
+ (sc->temp_high_val - 250) /
+ (sc->temp_room_cnt - sc->temp_high_cnt))));
+}
+
+static inline uint32_t
+temp_to_count(struct imx6_anatop_softc *sc, uint32_t temp)
+{
+
+ return ((sc->temp_room_cnt - sc->temp_high_cnt) *
+ (sc->temp_high_val - temp) / (sc->temp_high_val - 250) +
+ sc->temp_high_cnt);
+}
+
+static void
+temp_update_count(struct imx6_anatop_softc *sc)
+{
+ uint32_t val;
+
+ val = imx6_anatop_read_4(IMX6_ANALOG_TEMPMON_TEMPSENSE0);
+ if (!(val & IMX6_ANALOG_TEMPMON_TEMPSENSE0_VALID))
+ return;
+ sc->temp_last_cnt =
+ (val & IMX6_ANALOG_TEMPMON_TEMPSENSE0_TEMP_CNT_MASK) >>
+ IMX6_ANALOG_TEMPMON_TEMPSENSE0_TEMP_CNT_SHIFT;
}
static int
-imx6_anatop_detach(device_t dev)
+temp_sysctl_handler(SYSCTL_HANDLER_ARGS)
{
- struct imx6_anatop_softc *sc;
+ struct imx6_anatop_softc *sc = arg1;
+ uint32_t t;
- sc = device_get_softc(dev);
+ temp_update_count(sc);
+
+ t = temp_from_count(sc, sc->temp_last_cnt) + TZ_ZEROC;
+
+ return (sysctl_handle_int(oidp, &t, 0, req));
+}
+
+static int
+temp_throttle_sysctl_handler(SYSCTL_HANDLER_ARGS)
+{
+ struct imx6_anatop_softc *sc = arg1;
+ int err;
+ uint32_t temp;
+
+ temp = sc->temp_throttle_val + TZ_ZEROC;
+ err = sysctl_handle_int(oidp, &temp, 0, req);
+ if (temp < TZ_ZEROC)
+ return (ERANGE);
+ temp -= TZ_ZEROC;
+ if (err != 0 || req->newptr == NULL || temp == sc->temp_throttle_val)
+ return (err);
+
+ /* Value changed, update counts in softc and hardware. */
+ sc->temp_throttle_val = temp;
+ sc->temp_throttle_trigger_cnt = temp_to_count(sc, sc->temp_throttle_val);
+ sc->temp_throttle_reset_cnt = temp_to_count(sc, sc->temp_throttle_val - 100);
+ imx6_anatop_write_4(IMX6_ANALOG_TEMPMON_TEMPSENSE0_CLR,
+ IMX6_ANALOG_TEMPMON_TEMPSENSE0_ALARM_MASK);
+ imx6_anatop_write_4(IMX6_ANALOG_TEMPMON_TEMPSENSE0_SET,
+ (sc->temp_throttle_trigger_cnt <<
+ IMX6_ANALOG_TEMPMON_TEMPSENSE0_ALARM_SHIFT));
+ return (err);
+}
+
+static void
+tempmon_gofast(struct imx6_anatop_softc *sc)
+{
+
+ if (sc->cpu_curhz < sc->cpu_maxhz) {
+ cpufreq_set_clock(sc, sc->cpu_maxhz);
+ }
+}
+
+static void
+tempmon_goslow(struct imx6_anatop_softc *sc)
+{
+
+ if (sc->cpu_curhz > sc->cpu_minhz) {
+ cpufreq_set_clock(sc, sc->cpu_minhz);
+ }
+}
+
+static int
+tempmon_intr(void *arg)
+{
+ struct imx6_anatop_softc *sc = arg;
+
+ /*
+ * XXX Note that this code doesn't currently run (for some mysterious
+ * reason we just never get an interrupt), so the real monitoring is
+ * done by tempmon_throttle_check().
+ */
+ tempmon_goslow(sc);
+ /* XXX Schedule callout to speed back up eventually. */
+ return (FILTER_HANDLED);
+}
+
+static void
+tempmon_throttle_check(void *arg)
+{
+ struct imx6_anatop_softc *sc = arg;
- if (sc->mem_res != NULL)
- bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
+ /* Lower counts are higher temperatures. */
+ if (sc->temp_last_cnt < sc->temp_throttle_trigger_cnt)
+ tempmon_goslow(sc);
+ else if (sc->temp_last_cnt > (sc->temp_throttle_reset_cnt))
+ tempmon_gofast(sc);
- return (0);
+ callout_reset_sbt(&sc->temp_throttle_callout, sc->temp_throttle_delay,
+ 0, tempmon_throttle_check, sc, 0);
+
+}
+
+static void
+initialize_tempmon(struct imx6_anatop_softc *sc)
+{
+ uint32_t cal;
+ struct sysctl_ctx_list *ctx;
+
+ /*
+ * Fetch calibration data: a sensor count at room temperature (25C),
+ * a sensor count at a high temperature, and that temperature
+ */
+ cal = fsl_ocotp_read_4(FSL_OCOTP_ANA1);
+ sc->temp_room_cnt = (cal & 0xFFF00000) >> 20;
+ sc->temp_high_cnt = (cal & 0x000FFF00) >> 8;
+ sc->temp_high_val = (cal & 0x000000FF) * 10;
+
+ /*
+ * Throttle to a lower cpu freq at 10C below the "hot" temperature, and
+ * reset back to max cpu freq at 5C below the trigger.
+ */
+ sc->temp_throttle_val = sc->temp_high_val - 100;
+ sc->temp_throttle_trigger_cnt =
+ temp_to_count(sc, sc->temp_throttle_val);
+ sc->temp_throttle_reset_cnt =
+ temp_to_count(sc, sc->temp_throttle_val - 50);
+
+ /*
+ * Set the sensor to sample automatically at 16Hz (32.768KHz/0x800), set
+ * the throttle count, and begin making measurements.
+ */
+ imx6_anatop_write_4(IMX6_ANALOG_TEMPMON_TEMPSENSE1, 0x0800);
+ imx6_anatop_write_4(IMX6_ANALOG_TEMPMON_TEMPSENSE0,
+ (sc->temp_throttle_trigger_cnt <<
+ IMX6_ANALOG_TEMPMON_TEMPSENSE0_ALARM_SHIFT) |
+ IMX6_ANALOG_TEMPMON_TEMPSENSE0_MEASURE);
+
+ /*
+ * XXX Note that the alarm-interrupt feature isn't working yet, so
+ * we'll use a callout handler to check at 10Hz. Make sure we have an
+ * initial temperature reading before starting up the callouts so we
+ * don't get a bogus reading of zero.
+ */
+ while (sc->temp_last_cnt == 0)
+ temp_update_count(sc);
+ sc->temp_throttle_delay = 100 * SBT_1MS;
+ callout_init(&sc->temp_throttle_callout, 0);
+ callout_reset_sbt(&sc->temp_throttle_callout, sc->temp_throttle_delay,
+ 0, tempmon_throttle_check, sc, 0);
+
+ ctx = device_get_sysctl_ctx(sc->dev);
+ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
+ OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, sc, 0,
+ temp_sysctl_handler, "IK", "Current die temperature");
+ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
+ OID_AUTO, "throttle_temperature", CTLTYPE_INT | CTLFLAG_RW, sc,
+ 0, temp_throttle_sysctl_handler, "IK",
+ "Throttle CPU when exceeding this temperature");
+}
+
+static int
+imx6_anatop_detach(device_t dev)
+{
+
+ return (EBUSY);
}
static int
imx6_anatop_attach(device_t dev)
{
struct imx6_anatop_softc *sc;
- int err, rid;
+ int err;
sc = device_get_softc(dev);
+ sc->dev = dev;
/* Allocate bus_space resources. */
- rid = 0;
- sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
- RF_ACTIVE);
- if (sc->mem_res == NULL) {
- device_printf(dev, "Cannot allocate memory resources\n");
+ if (bus_alloc_resources(dev, imx6_anatop_spec, sc->res)) {
+ device_printf(dev, "Cannot allocate resources\n");
err = ENXIO;
goto out;
}
+ err = bus_setup_intr(dev, sc->res[IRQRES], INTR_TYPE_MISC | INTR_MPSAFE,
+ tempmon_intr, NULL, sc, &sc->temp_intrhand);
+ if (err != 0)
+ goto out;
+
imx6_anatop_sc = sc;
+
+ /*
+ * Other code seen on the net sets this SELFBIASOFF flag around the same
+ * time the temperature sensor is set up, although it's unclear how the
+ * two are related (if at all).
+ */
+ imx6_anatop_write_4(IMX6_ANALOG_PMU_MISC0_SET,
+ IMX6_ANALOG_PMU_MISC0_SELFBIASOFF);
+
+ cpufreq_initialize(sc);
+ initialize_tempmon(sc);
+
err = 0;
out:
- if (err != 0)
- imx6_anatop_detach(dev);
+ if (err != 0) {
+ bus_release_resources(dev, imx6_anatop_spec, sc->res);
+ }
return (err);
}
@@ -129,7 +482,7 @@ imx6_anatop_probe(device_t dev)
if (!ofw_bus_status_okay(dev))
return (ENXIO);
- if (ofw_bus_is_compatible(dev, "fsl,imx6q-anatop") == 0)
+ if (ofw_bus_is_compatible(dev, "fsl,imx6q-anatop") == 0)
return (ENXIO);
device_set_desc(dev, "Freescale i.MX6 Analog PLLs and Power");
@@ -137,6 +490,16 @@ imx6_anatop_probe(device_t dev)
return (BUS_PROBE_DEFAULT);
}
+uint32_t
+imx6_get_cpu_clock()
+{
+ uint32_t div;
+
+ div = imx6_anatop_read_4(IMX6_ANALOG_CCM_PLL_ARM) &
+ IMX6_ANALOG_CCM_PLL_ARM_DIV_MASK;
+ return (cpufreq_hz_from_div(imx6_anatop_sc, div));
+}
+
static device_method_t imx6_anatop_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, imx6_anatop_probe),
diff --git a/sys/arm/freescale/imx/imx6_anatopreg.h b/sys/arm/freescale/imx/imx6_anatopreg.h
index bcff808..bb75735 100644
--- a/sys/arm/freescale/imx/imx6_anatopreg.h
+++ b/sys/arm/freescale/imx/imx6_anatopreg.h
@@ -33,6 +33,10 @@
#define IMX6_ANALOG_CCM_PLL_ARM_SET 0x004
#define IMX6_ANALOG_CCM_PLL_ARM_CLR 0x008
#define IMX6_ANALOG_CCM_PLL_ARM_TOG 0x00C
+#define IMX6_ANALOG_CCM_PLL_ARM_DIV_MASK 0x7F
+#define IMX6_ANALOG_CCM_PLL_ARM_LOCK (1U << 31)
+#define IMX6_ANALOG_CCM_PLL_ARM_BYPASS (1 << 16)
+#define IMX6_ANALOG_CCM_PLL_ARM_CLK_SRC_MASK (0x03 << 16)
#define IMX6_ANALOG_CCM_PLL_USB1 0x010
#define IMX6_ANALOG_CCM_PLL_USB1_SET 0x014
#define IMX6_ANALOG_CCM_PLL_USB1_CLR 0x018
@@ -81,6 +85,7 @@
#define IMX6_ANALOG_CCM_PFD_528_SET 0x104
#define IMX6_ANALOG_CCM_PFD_528_CLR 0x108
#define IMX6_ANALOG_CCM_PFD_528_TOG 0x10C
+
#define IMX6_ANALOG_PMU_REG_CORE 0x140
#define IMX6_ANALOG_PMU_REG2_TARG_SHIFT 18
#define IMX6_ANALOG_PMU_REG2_TARG_MASK \
@@ -92,14 +97,56 @@
#define IMX6_ANALOG_PMU_REG0_TARG_MASK \
(0x1f << IMX6_ANALOG_PMU_REG0_TARG_SHIFT)
-#define IMX6_ANALOG_CCM_MISC0 0x150
-#define IMX6_ANALOG_CCM_MISC0_SET 0x154
-#define IMX6_ANALOG_CCM_MISC0_CLR 0x158
-#define IMX6_ANALOG_CCM_MISC0_TOG 0x15C
-#define IMX6_ANALOG_CCM_MISC2 0x170
-#define IMX6_ANALOG_CCM_MISC2_SET 0x174
-#define IMX6_ANALOG_CCM_MISC2_CLR 0x178
-#define IMX6_ANALOG_CCM_MISC2_TOG 0x17C
+#define IMX6_ANALOG_PMU_MISC0 0x150
+#define IMX6_ANALOG_PMU_MISC0_SET 0x154
+#define IMX6_ANALOG_PMU_MISC0_CLR 0x158
+#define IMX6_ANALOG_PMU_MISC0_TOG 0x15C
+#define IMX6_ANALOG_PMU_MISC0_SELFBIASOFF (1 << 3)
+
+#define IMX6_ANALOG_PMU_MISC1 0x160
+#define IMX6_ANALOG_PMU_MISC1_SET 0x164
+#define IMX6_ANALOG_PMU_MISC1_CLR 0x168
+#define IMX6_ANALOG_PMU_MISC1_TOG 0x16C
+#define IMX6_ANALOG_PMU_MISC1_IRQ_TEMPSENSE (1 << 29)
+
+#define IMX6_ANALOG_PMU_MISC2 0x170
+#define IMX6_ANALOG_PMU_MISC2_SET 0x174
+#define IMX6_ANALOG_PMU_MISC2_CLR 0x178
+#define IMX6_ANALOG_PMU_MISC2_TOG 0x17C
+
+/*
+ * Note that the ANALOG_CCM_MISCn registers are the same as the PMU_MISCn
+ * registers; some bits conceptually belong to the PMU and some to the CCM.
+ */
+#define IMX6_ANALOG_CCM_MISC0 IMX6_ANALOG_PMU_MISC0
+#define IMX6_ANALOG_CCM_MISC0_SET IMX6_ANALOG_PMU_MISC0_SET
+#define IMX6_ANALOG_CCM_MISC0_CLR IMX6_ANALOG_PMU_MISC0_CLR
+#define IMX6_ANALOG_CCM_MISC0_TOG IMX6_ANALOG_PMU_MISC0_TOG
+
+#define IMX6_ANALOG_CCM_MISC2 IMX6_ANALOG_PMU_MISC2
+#define IMX6_ANALOG_CCM_MISC2_SET IMX6_ANALOG_PMU_MISC2_SET
+#define IMX6_ANALOG_CCM_MISC2_CLR IMX6_ANALOG_PMU_MISC2_CLR
+#define IMX6_ANALOG_CCM_MISC2_TOG IMX6_ANALOG_PMU_MISC2_TOG
+
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0 0x180
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_SET 0x184
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_CLR 0x188
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_TOG 0x18C
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_TOG 0x18C
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_ALARM_SHIFT 20
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_ALARM_MASK \
+ (0xfff << IMX6_ANALOG_TEMPMON_TEMPSENSE0_ALARM_SHIFT)
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_TEMP_CNT_SHIFT 8
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_TEMP_CNT_MASK \
+ (0xfff << IMX6_ANALOG_TEMPMON_TEMPSENSE0_TEMP_CNT_SHIFT)
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_VALID 0x4
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_MEASURE 0x2
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE0_POWER_DOWN 0x1
+
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE1 0x190
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE1_SET 0x194
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE1_CLR 0x198
+#define IMX6_ANALOG_TEMPMON_TEMPSENSE1_TOG 0x19C
#define IMX6_ANALOG_USB1_VBUS_DETECT 0x1A0
#define IMX6_ANALOG_USB1_VBUS_DETECT_SET 0x1A4
diff --git a/sys/arm/freescale/imx/imx6_anatopvar.h b/sys/arm/freescale/imx/imx6_anatopvar.h
index 1974089..5ede711 100644
--- a/sys/arm/freescale/imx/imx6_anatopvar.h
+++ b/sys/arm/freescale/imx/imx6_anatopvar.h
@@ -40,4 +40,6 @@
uint32_t imx6_anatop_read_4(bus_size_t _offset);
void imx6_anatop_write_4(bus_size_t _offset, uint32_t _value);
+uint32_t imx6_get_cpu_clock(void);
+
#endif
diff --git a/sys/arm/freescale/imx/imx6_machdep.c b/sys/arm/freescale/imx/imx6_machdep.c
index 9f738a5..abca91f 100644
--- a/sys/arm/freescale/imx/imx6_machdep.c
+++ b/sys/arm/freescale/imx/imx6_machdep.c
@@ -55,9 +55,6 @@ void
initarm_early_init(void)
{
- /* XXX - Get rid of this stuff soon. */
- boothowto |= RB_VERBOSE|RB_MULTIPLE;
- bootverbose = 1;
}
void
@@ -190,3 +187,27 @@ u_int imx_soc_type()
return (IMXSOC_6Q);
}
+/*
+ * Early putc routine for EARLY_PRINTF support. To use, add to kernel config:
+ * option SOCDEV_PA=0x02000000
+ * option SOCDEV_VA=0x02000000
+ * option EARLY_PRINTF
+ * Resist the temptation to change the #if 0 to #ifdef EARLY_PRINTF here. It
+ * makes sense now, but if multiple SOCs do that it will make early_putc another
+ * duplicate symbol to be eliminated on the path to a generic kernel.
+ */
+#if 0
+static void
+imx6_early_putc(int c)
+{
+ volatile uint32_t * UART_STAT_REG = (uint32_t *)0x02020098;
+ volatile uint32_t * UART_TX_REG = (uint32_t *)0x02020040;
+ const uint32_t UART_TXRDY = (1 << 3);
+
+ while ((*UART_STAT_REG & UART_TXRDY) == 0)
+ continue;
+ *UART_TX_REG = c;
+}
+early_putc_t *early_putc = imx6_early_putc;
+#endif
+
diff --git a/sys/arm/freescale/vybrid/files.vybrid b/sys/arm/freescale/vybrid/files.vybrid
index 756beb9..890e23e 100644
--- a/sys/arm/freescale/vybrid/files.vybrid
+++ b/sys/arm/freescale/vybrid/files.vybrid
@@ -29,4 +29,5 @@ arm/freescale/vybrid/vf_nfc.c optional nand
arm/freescale/vybrid/vf_ehci.c optional ehci
arm/freescale/vybrid/vf_gpio.c optional gpio
arm/freescale/vybrid/vf_uart.c optional uart
+arm/freescale/vybrid/vf_sai.c optional sound
dev/ffec/if_ffec.c optional ffec
diff --git a/sys/arm/freescale/vybrid/vf_sai.c b/sys/arm/freescale/vybrid/vf_sai.c
new file mode 100644
index 0000000..018b4f6
--- /dev/null
+++ b/sys/arm/freescale/vybrid/vf_sai.c
@@ -0,0 +1,805 @@
+/*-
+ * Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * Vybrid Family Synchronous Audio Interface (SAI)
+ * Chapter 51, Vybrid Reference Manual, Rev. 5, 07/2013
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/malloc.h>
+#include <sys/rman.h>
+#include <sys/timeet.h>
+#include <sys/timetc.h>
+#include <sys/watchdog.h>
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/chip.h>
+#include <mixer_if.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+#include <machine/cpu.h>
+#include <machine/intr.h>
+
+#include <arm/freescale/vybrid/vf_common.h>
+#include <arm/freescale/vybrid/vf_dmamux.h>
+#include <arm/freescale/vybrid/vf_edma.h>
+
+#define I2S_TCSR 0x00 /* SAI Transmit Control */
+#define I2S_TCR1 0x04 /* SAI Transmit Configuration 1 */
+#define I2S_TCR2 0x08 /* SAI Transmit Configuration 2 */
+#define I2S_TCR3 0x0C /* SAI Transmit Configuration 3 */
+#define I2S_TCR4 0x10 /* SAI Transmit Configuration 4 */
+#define I2S_TCR5 0x14 /* SAI Transmit Configuration 5 */
+#define I2S_TDR0 0x20 /* SAI Transmit Data */
+#define I2S_TFR0 0x40 /* SAI Transmit FIFO */
+#define I2S_TMR 0x60 /* SAI Transmit Mask */
+#define I2S_RCSR 0x80 /* SAI Receive Control */
+#define I2S_RCR1 0x84 /* SAI Receive Configuration 1 */
+#define I2S_RCR2 0x88 /* SAI Receive Configuration 2 */
+#define I2S_RCR3 0x8C /* SAI Receive Configuration 3 */
+#define I2S_RCR4 0x90 /* SAI Receive Configuration 4 */
+#define I2S_RCR5 0x94 /* SAI Receive Configuration 5 */
+#define I2S_RDR0 0xA0 /* SAI Receive Data */
+#define I2S_RFR0 0xC0 /* SAI Receive FIFO */
+#define I2S_RMR 0xE0 /* SAI Receive Mask */
+
+#define TCR1_TFW_M 0x1f /* Transmit FIFO Watermark Mask */
+#define TCR1_TFW_S 0 /* Transmit FIFO Watermark Shift */
+#define TCR2_MSEL_M 0x3 /* MCLK Select Mask*/
+#define TCR2_MSEL_S 26 /* MCLK Select Shift*/
+#define TCR2_BCP (1 << 25) /* Bit Clock Polarity */
+#define TCR2_BCD (1 << 24) /* Bit Clock Direction */
+#define TCR3_TCE (1 << 16) /* Transmit Channel Enable */
+#define TCR4_FRSZ_M 0x1f /* Frame size Mask */
+#define TCR4_FRSZ_S 16 /* Frame size Shift */
+#define TCR4_SYWD_M 0x1f /* Sync Width Mask */
+#define TCR4_SYWD_S 8 /* Sync Width Shift */
+#define TCR4_MF (1 << 4) /* MSB First */
+#define TCR4_FSE (1 << 3) /* Frame Sync Early */
+#define TCR4_FSP (1 << 1) /* Frame Sync Polarity Low */
+#define TCR4_FSD (1 << 0) /* Frame Sync Direction Master */
+#define TCR5_FBT_M 0x1f /* First Bit Shifted */
+#define TCR5_FBT_S 8 /* First Bit Shifted */
+#define TCR5_W0W_M 0x1f /* Word 0 Width */
+#define TCR5_W0W_S 16 /* Word 0 Width */
+#define TCR5_WNW_M 0x1f /* Word N Width */
+#define TCR5_WNW_S 24 /* Word N Width */
+#define TCSR_TE (1 << 31) /* Transmitter Enable */
+#define TCSR_BCE (1 << 28) /* Bit Clock Enable */
+#define TCSR_FRDE (1 << 0) /* FIFO Request DMA Enable */
+
+#define SAI_NCHANNELS 1
+
+static MALLOC_DEFINE(M_SAI, "sai", "sai audio");
+
+struct sai_rate {
+ uint32_t speed;
+ uint32_t div; /* Bit Clock Divide. Division value is (div + 1) * 2. */
+ uint32_t mfi; /* PLL4 Multiplication Factor Integer */
+ uint32_t mfn; /* PLL4 Multiplication Factor Numerator */
+ uint32_t mfd; /* PLL4 Multiplication Factor Denominator */
+};
+
+/*
+ * Bit clock divider formula
+ * (div + 1) * 2 = MCLK/(nch * LRCLK * bits/1000000),
+ * where:
+ * MCLK - master clock
+ * nch - number of channels
+ * LRCLK - left right clock
+ * e.g. (div + 1) * 2 = 16.9344/(2 * 44100 * 24/1000000)
+ *
+ * Example for 96khz, 24bit, 18.432 Mhz mclk (192fs)
+ * { 96000, 1, 18, 40176000, 93000000 },
+ */
+
+static struct sai_rate rate_map[] = {
+ { 44100, 7, 33, 80798400, 93000000 }, /* 33.8688 Mhz */
+ { 96000, 3, 36, 80352000, 93000000 }, /* 36.864 Mhz */
+ { 192000, 1, 36, 80352000, 93000000 }, /* 36.864 Mhz */
+ { 0, 0 },
+};
+
+struct sc_info {
+ struct resource *res[2];
+ bus_space_tag_t bst;
+ bus_space_handle_t bsh;
+ device_t dev;
+ struct mtx *lock;
+ uint32_t speed;
+ uint32_t period;
+ void *ih;
+ int pos;
+ int dma_size;
+ bus_dma_tag_t dma_tag;
+ bus_dmamap_t dma_map;
+ bus_addr_t buf_base_phys;
+ uint32_t *buf_base;
+ struct tcd_conf *tcd;
+ struct sai_rate *sr;
+ struct edma_softc *edma_sc;
+ int edma_chnum;
+};
+
+/* Channel registers */
+struct sc_chinfo {
+ struct snd_dbuf *buffer;
+ struct pcm_channel *channel;
+ struct sc_pcminfo *parent;
+
+ /* Channel information */
+ uint32_t dir;
+ uint32_t format;
+
+ /* Flags */
+ uint32_t run;
+};
+
+/* PCM device private data */
+struct sc_pcminfo {
+ device_t dev;
+ uint32_t (*ih) (struct sc_pcminfo *scp);
+ uint32_t chnum;
+ struct sc_chinfo chan[SAI_NCHANNELS];
+ struct sc_info *sc;
+};
+
+static struct resource_spec sai_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE },
+ { -1, 0 }
+};
+
+static int setup_dma(struct sc_pcminfo *scp);
+static void setup_sai(struct sc_info *);
+static void sai_configure_clock(struct sc_info *);
+
+/*
+ * Mixer interface.
+ */
+
+static int
+saimixer_init(struct snd_mixer *m)
+{
+ struct sc_pcminfo *scp;
+ struct sc_info *sc;
+ int mask;
+
+ scp = mix_getdevinfo(m);
+ sc = scp->sc;
+
+ if (sc == NULL)
+ return -1;
+
+ mask = SOUND_MASK_PCM;
+
+ snd_mtxlock(sc->lock);
+ pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL);
+ mix_setdevs(m, mask);
+ snd_mtxunlock(sc->lock);
+
+ return (0);
+}
+
+static int
+saimixer_set(struct snd_mixer *m, unsigned dev,
+ unsigned left, unsigned right)
+{
+ struct sc_pcminfo *scp;
+
+ scp = mix_getdevinfo(m);
+
+#if 0
+ device_printf(scp->dev, "saimixer_set() %d %d\n",
+ left, right);
+#endif
+
+ return (0);
+}
+
+static kobj_method_t saimixer_methods[] = {
+ KOBJMETHOD(mixer_init, saimixer_init),
+ KOBJMETHOD(mixer_set, saimixer_set),
+ KOBJMETHOD_END
+};
+MIXER_DECLARE(saimixer);
+
+/*
+ * Channel interface.
+ */
+
+static void *
+saichan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
+ struct pcm_channel *c, int dir)
+{
+ struct sc_pcminfo *scp;
+ struct sc_chinfo *ch;
+ struct sc_info *sc;
+
+ scp = (struct sc_pcminfo *)devinfo;
+ sc = scp->sc;
+
+ snd_mtxlock(sc->lock);
+ ch = &scp->chan[0];
+ ch->dir = dir;
+ ch->run = 0;
+ ch->buffer = b;
+ ch->channel = c;
+ ch->parent = scp;
+ snd_mtxunlock(sc->lock);
+
+ if (sndbuf_setup(ch->buffer, sc->buf_base, sc->dma_size) != 0) {
+ device_printf(scp->dev, "Can't setup sndbuf.\n");
+ return NULL;
+ }
+
+ return ch;
+}
+
+static int
+saichan_free(kobj_t obj, void *data)
+{
+ struct sc_chinfo *ch = data;
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+
+#if 0
+ device_printf(scp->dev, "saichan_free()\n");
+#endif
+
+ snd_mtxlock(sc->lock);
+ /* TODO: free channel buffer */
+ snd_mtxunlock(sc->lock);
+
+ return (0);
+}
+
+static int
+saichan_setformat(kobj_t obj, void *data, uint32_t format)
+{
+ struct sc_chinfo *ch = data;
+
+ ch->format = format;
+
+ return (0);
+}
+
+static uint32_t
+saichan_setspeed(kobj_t obj, void *data, uint32_t speed)
+{
+ struct sc_pcminfo *scp;
+ struct sc_chinfo *ch;
+ struct sai_rate *sr;
+ struct sc_info *sc;
+ int threshold;
+ int i;
+
+ ch = data;
+ scp = ch->parent;
+ sc = scp->sc;
+
+ sr = NULL;
+
+ /* First look for equal frequency. */
+ for (i = 0; rate_map[i].speed != 0; i++) {
+ if (rate_map[i].speed == speed)
+ sr = &rate_map[i];
+ }
+
+ /* If no match, just find nearest. */
+ if (sr == NULL) {
+ for (i = 0; rate_map[i].speed != 0; i++) {
+ sr = &rate_map[i];
+ threshold = sr->speed + ((rate_map[i + 1].speed != 0) ?
+ ((rate_map[i + 1].speed - sr->speed) >> 1) : 0);
+ if (speed < threshold)
+ break;
+ }
+ }
+
+ sc->sr = sr;
+
+ sai_configure_clock(sc);
+
+ return (sr->speed);
+}
+
+static void
+sai_configure_clock(struct sc_info *sc)
+{
+ struct sai_rate *sr;
+ int reg;
+
+ sr = sc->sr;
+
+ /*
+ * Manual says that TCR/RCR registers must not be
+ * altered when TCSR[TE] is set.
+ * We ignore it since we have problem sometimes
+ * after re-enabling transmitter (DMA goes stall).
+ */
+
+ reg = READ4(sc, I2S_TCR2);
+ reg &= ~(0xff << 0);
+ reg |= (sr->div << 0);
+ WRITE4(sc, I2S_TCR2, reg);
+
+ pll4_configure_output(sr->mfi, sr->mfn, sr->mfd);
+}
+
+static uint32_t
+saichan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)
+{
+ struct sc_chinfo *ch = data;
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+
+ sndbuf_resize(ch->buffer, sc->dma_size / blocksize, blocksize);
+
+ sc->period = sndbuf_getblksz(ch->buffer);
+ return (sc->period);
+}
+
+uint32_t sai_dma_intr(void *arg, int chn);
+uint32_t
+sai_dma_intr(void *arg, int chn)
+{
+ struct sc_pcminfo *scp;
+ struct sc_chinfo *ch;
+ struct sc_info *sc;
+ struct tcd_conf *tcd;
+
+ scp = arg;
+ ch = &scp->chan[0];
+
+ sc = scp->sc;
+ tcd = sc->tcd;
+
+ sc->pos += (tcd->nbytes * tcd->nmajor);
+ if (sc->pos >= sc->dma_size)
+ sc->pos -= sc->dma_size;
+
+ if (ch->run)
+ chn_intr(ch->channel);
+
+ return (0);
+}
+
+static int
+find_edma_controller(struct sc_info *sc)
+{
+ struct edma_softc *edma_sc;
+ phandle_t node, edma_node;
+ int edma_src_transmit;
+ int edma_mux_group;
+ int edma_device_id;
+ device_t edma_dev;
+ int dts_value;
+ int len;
+ int i;
+
+ if ((node = ofw_bus_get_node(sc->dev)) == -1)
+ return (ENXIO);
+
+ if ((len = OF_getproplen(node, "edma-controller")) <= 0)
+ return (ENXIO);
+ if ((len = OF_getproplen(node, "edma-src-transmit")) <= 0)
+ return (ENXIO);
+ if ((len = OF_getproplen(node, "edma-mux-group")) <= 0)
+ return (ENXIO);
+
+ OF_getprop(node, "edma-src-transmit", &dts_value, len);
+ edma_src_transmit = fdt32_to_cpu(dts_value);
+ OF_getprop(node, "edma-mux-group", &dts_value, len);
+ edma_mux_group = fdt32_to_cpu(dts_value);
+ OF_getprop(node, "edma-controller", &dts_value, len);
+ edma_node = OF_xref_phandle(fdt32_to_cpu(dts_value));
+
+ if ((len = OF_getproplen(edma_node, "device-id")) <= 0) {
+ return (ENXIO);
+ };
+
+ OF_getprop(edma_node, "device-id", &dts_value, len);
+ edma_device_id = fdt32_to_cpu(dts_value);
+
+ edma_sc = NULL;
+
+ for (i = 0; i < EDMA_NUM_DEVICES; i++) {
+ edma_dev = devclass_get_device(devclass_find("edma"), i);
+ if (edma_dev) {
+ edma_sc = device_get_softc(edma_dev);
+ if (edma_sc->device_id == edma_device_id) {
+ /* found */
+ break;
+ };
+
+ edma_sc = NULL;
+ };
+ };
+
+ if (edma_sc == NULL) {
+ device_printf(sc->dev, "no eDMA. can't operate\n");
+ return (ENXIO);
+ };
+
+ sc->edma_sc = edma_sc;
+
+ sc->edma_chnum = edma_sc->channel_configure(edma_sc, edma_mux_group,
+ edma_src_transmit);
+ if (sc->edma_chnum < 0) {
+ /* cant setup eDMA */
+ return (ENXIO);
+ };
+
+ return (0);
+};
+
+static int
+setup_dma(struct sc_pcminfo *scp)
+{
+ struct tcd_conf *tcd;
+ struct sc_info *sc;
+
+ sc = scp->sc;
+
+ tcd = malloc(sizeof(struct tcd_conf), M_DEVBUF, M_WAITOK | M_ZERO);
+ tcd->channel = sc->edma_chnum;
+ tcd->ih = sai_dma_intr;
+ tcd->ih_user = scp;
+ tcd->saddr = sc->buf_base_phys;
+ tcd->daddr = rman_get_start(sc->res[0]) + I2S_TDR0;
+
+ /*
+ * Bytes to transfer per each minor loop.
+ * Hardware FIFO buffer size is 32x32bits.
+ */
+ tcd->nbytes = 64;
+
+ tcd->nmajor = 512;
+ tcd->smod = 17; /* dma_size range */
+ tcd->dmod = 0;
+ tcd->esg = 0;
+ tcd->soff = 0x4;
+ tcd->doff = 0;
+ tcd->ssize = 0x2;
+ tcd->dsize = 0x2;
+ tcd->slast = 0;
+ tcd->dlast_sga = 0;
+
+ sc->tcd = tcd;
+
+ sc->edma_sc->dma_setup(sc->edma_sc, sc->tcd);
+
+ return (0);
+}
+
+static int
+saichan_trigger(kobj_t obj, void *data, int go)
+{
+ struct sc_chinfo *ch = data;
+ struct sc_pcminfo *scp = ch->parent;
+ struct sc_info *sc = scp->sc;
+
+ snd_mtxlock(sc->lock);
+
+ switch (go) {
+ case PCMTRIG_START:
+#if 0
+ device_printf(scp->dev, "trigger start\n");
+#endif
+ ch->run = 1;
+ break;
+
+ case PCMTRIG_STOP:
+ case PCMTRIG_ABORT:
+#if 0
+ device_printf(scp->dev, "trigger stop or abort\n");
+#endif
+ ch->run = 0;
+ break;
+ }
+
+ snd_mtxunlock(sc->lock);
+
+ return (0);
+}
+
+static uint32_t
+saichan_getptr(kobj_t obj, void *data)
+{
+ struct sc_pcminfo *scp;
+ struct sc_chinfo *ch;
+ struct sc_info *sc;
+
+ ch = data;
+ scp = ch->parent;
+ sc = scp->sc;
+
+ return (sc->pos);
+}
+
+static uint32_t sai_pfmt[] = {
+ /*
+ * eDMA doesn't allow 24-bit coping,
+ * so we use 32.
+ */
+ SND_FORMAT(AFMT_S32_LE, 2, 0),
+ 0
+};
+
+static struct pcmchan_caps sai_pcaps = {44100, 192000, sai_pfmt, 0};
+
+static struct pcmchan_caps *
+saichan_getcaps(kobj_t obj, void *data)
+{
+
+ return (&sai_pcaps);
+}
+
+static kobj_method_t saichan_methods[] = {
+ KOBJMETHOD(channel_init, saichan_init),
+ KOBJMETHOD(channel_free, saichan_free),
+ KOBJMETHOD(channel_setformat, saichan_setformat),
+ KOBJMETHOD(channel_setspeed, saichan_setspeed),
+ KOBJMETHOD(channel_setblocksize, saichan_setblocksize),
+ KOBJMETHOD(channel_trigger, saichan_trigger),
+ KOBJMETHOD(channel_getptr, saichan_getptr),
+ KOBJMETHOD(channel_getcaps, saichan_getcaps),
+ KOBJMETHOD_END
+};
+CHANNEL_DECLARE(saichan);
+
+static int
+sai_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (!ofw_bus_is_compatible(dev, "fsl,mvf600-sai"))
+ return (ENXIO);
+
+ device_set_desc(dev, "Vybrid Family Synchronous Audio Interface");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static void
+sai_intr(void *arg)
+{
+ struct sc_pcminfo *scp;
+ struct sc_info *sc;
+
+ scp = arg;
+ sc = scp->sc;
+
+ device_printf(sc->dev, "Error I2S_TCSR == 0x%08x\n",
+ READ4(sc, I2S_TCSR));
+}
+
+static void
+setup_sai(struct sc_info *sc)
+{
+ int reg;
+
+ /*
+ * TCR/RCR registers must not be altered when TCSR[TE] is set.
+ */
+
+ reg = READ4(sc, I2S_TCSR);
+ reg &= ~(TCSR_BCE | TCSR_TE | TCSR_FRDE);
+ WRITE4(sc, I2S_TCSR, reg);
+
+ reg = READ4(sc, I2S_TCR3);
+ reg &= ~(TCR3_TCE);
+ WRITE4(sc, I2S_TCR3, reg);
+
+ reg = (64 << TCR1_TFW_S);
+ WRITE4(sc, I2S_TCR1, reg);
+
+ reg = READ4(sc, I2S_TCR2);
+ reg &= ~(TCR2_MSEL_M << TCR2_MSEL_S);
+ reg |= (1 << TCR2_MSEL_S);
+ reg |= (TCR2_BCP | TCR2_BCD);
+ WRITE4(sc, I2S_TCR2, reg);
+
+ sai_configure_clock(sc);
+
+ reg = READ4(sc, I2S_TCR3);
+ reg |= (TCR3_TCE);
+ WRITE4(sc, I2S_TCR3, reg);
+
+ /* Configure to 32-bit I2S mode */
+ reg = READ4(sc, I2S_TCR4);
+ reg &= ~(TCR4_FRSZ_M << TCR4_FRSZ_S);
+ reg |= (1 << TCR4_FRSZ_S); /* 2 words per frame */
+ reg &= ~(TCR4_SYWD_M << TCR4_SYWD_S);
+ reg |= (23 << TCR4_SYWD_S);
+ reg |= (TCR4_MF | TCR4_FSE | TCR4_FSP | TCR4_FSD);
+ WRITE4(sc, I2S_TCR4, reg);
+
+ reg = READ4(sc, I2S_TCR5);
+ reg &= ~(TCR5_W0W_M << TCR5_W0W_S);
+ reg |= (23 << TCR5_W0W_S);
+ reg &= ~(TCR5_WNW_M << TCR5_WNW_S);
+ reg |= (23 << TCR5_WNW_S);
+ reg &= ~(TCR5_FBT_M << TCR5_FBT_S);
+ reg |= (31 << TCR5_FBT_S);
+ WRITE4(sc, I2S_TCR5, reg);
+
+ /* Enable transmitter */
+ reg = READ4(sc, I2S_TCSR);
+ reg |= (TCSR_BCE | TCSR_TE | TCSR_FRDE);
+ reg |= (1 << 10); /* FEIE */
+ WRITE4(sc, I2S_TCSR, reg);
+}
+
+
+static void
+sai_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err)
+{
+ bus_addr_t *addr;
+
+ if (err)
+ return;
+
+ addr = (bus_addr_t*)arg;
+ *addr = segs[0].ds_addr;
+}
+
+static int
+sai_attach(device_t dev)
+{
+ char status[SND_STATUSLEN];
+ struct sc_pcminfo *scp;
+ struct sc_info *sc;
+ int err;
+
+ sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
+ sc->dev = dev;
+ sc->sr = &rate_map[0];
+ sc->pos = 0;
+
+ sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sai softc");
+ if (sc->lock == NULL) {
+ device_printf(dev, "Cant create mtx\n");
+ return (ENXIO);
+ }
+
+ if (bus_alloc_resources(dev, sai_spec, sc->res)) {
+ device_printf(dev, "could not allocate resources\n");
+ return (ENXIO);
+ }
+
+ /* Memory interface */
+ sc->bst = rman_get_bustag(sc->res[0]);
+ sc->bsh = rman_get_bushandle(sc->res[0]);
+
+ /* eDMA */
+ if (find_edma_controller(sc)) {
+ device_printf(dev, "could not find active eDMA\n");
+ return (ENXIO);
+ }
+
+ /* Setup PCM */
+ scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
+ scp->sc = sc;
+ scp->dev = dev;
+
+ /* DMA */
+ sc->dma_size = 131072;
+
+ /*
+ * Must use dma_size boundary as modulo feature required.
+ * Modulo feature allows setup circular buffer.
+ */
+
+ err = bus_dma_tag_create(
+ bus_get_dma_tag(sc->dev),
+ 4, sc->dma_size, /* alignment, boundary */
+ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
+ BUS_SPACE_MAXADDR, /* highaddr */
+ NULL, NULL, /* filter, filterarg */
+ sc->dma_size, 1, /* maxsize, nsegments */
+ sc->dma_size, 0, /* maxsegsize, flags */
+ NULL, NULL, /* lockfunc, lockarg */
+ &sc->dma_tag);
+
+ err = bus_dmamem_alloc(sc->dma_tag, (void **)&sc->buf_base,
+ BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &sc->dma_map);
+ if (err) {
+ device_printf(dev, "cannot allocate framebuffer\n");
+ return (ENXIO);
+ }
+
+ err = bus_dmamap_load(sc->dma_tag, sc->dma_map, sc->buf_base,
+ sc->dma_size, sai_dmamap_cb, &sc->buf_base_phys, BUS_DMA_NOWAIT);
+ if (err) {
+ device_printf(dev, "cannot load DMA map\n");
+ return (ENXIO);
+ }
+
+ bzero(sc->buf_base, sc->dma_size);
+
+ /* Setup interrupt handler */
+ err = bus_setup_intr(dev, sc->res[1], INTR_MPSAFE | INTR_TYPE_AV,
+ NULL, sai_intr, scp, &sc->ih);
+ if (err) {
+ device_printf(dev, "Unable to alloc interrupt resource.\n");
+ return (ENXIO);
+ }
+
+ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
+
+ err = pcm_register(dev, scp, 1, 0);
+ if (err) {
+ device_printf(dev, "Can't register pcm.\n");
+ return (ENXIO);
+ }
+
+ scp->chnum = 0;
+ pcm_addchan(dev, PCMDIR_PLAY, &saichan_class, scp);
+ scp->chnum++;
+
+ snprintf(status, SND_STATUSLEN, "at simplebus");
+ pcm_setstatus(dev, status);
+
+ mixer_init(dev, &saimixer_class, scp);
+
+ setup_dma(scp);
+ setup_sai(sc);
+
+ return (0);
+}
+
+static device_method_t sai_pcm_methods[] = {
+ DEVMETHOD(device_probe, sai_probe),
+ DEVMETHOD(device_attach, sai_attach),
+ { 0, 0 }
+};
+
+static driver_t sai_pcm_driver = {
+ "pcm",
+ sai_pcm_methods,
+ PCM_SOFTC_SIZE,
+};
+
+DRIVER_MODULE(sai, simplebus, sai_pcm_driver, pcm_devclass, 0, 0);
+MODULE_DEPEND(sai, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
+MODULE_VERSION(sai, 1);
OpenPOWER on IntegriCloud