summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
authormartin <martin@NetBSD.org>2020-01-21 10:39:58 +0000
committermartin <martin@NetBSD.org>2020-01-21 10:39:58 +0000
commit836761697f870c34cced7dd6a90759dabb3be37b (patch)
tree811887aea2f2cdbc70b3fdf5d05661d4ab73a9fd /sys/dev
parent3e51e2695135bd72af4ebb10b880b24b00d170bc (diff)
Pull up following revision(s) (requested by mrg in ticket #616):
sys/dev/ic/anx_dp.c: revision 1.2 sys/arch/evbarm/conf/GENERIC64: revision 1.126 sys/dev/ic/anx_dp.h: revision 1.1 sys/arch/evbarm/conf/GENERIC64: revision 1.128 sys/dev/ic/anx_dp.h: revision 1.2 sys/dev/fdt/dwcmmc_fdt.c: revision 1.9 sys/dev/i2c/cwfg.c: revision 1.1 sys/conf/files: revision 1.1247 sys/dev/fdt/pwm_backlight.c: revision 1.5 sys/dev/fdt/pwm_backlight.c: revision 1.6 sys/arch/arm/rockchip/rk3399_cru.c: revision 1.14 sys/arch/arm/rockchip/rk3399_cru.c: revision 1.15 sys/arch/arm/rockchip/rk3399_cru.c: revision 1.16 sys/arch/arm/rockchip/rk3399_cru.c: revision 1.17 sys/dev/ic/dwc_mmc.c: revision 1.20 sys/arch/arm/rockchip/rk3399_cru.c: revision 1.18 sys/arch/arm/rockchip/rk3399_cru.c: revision 1.19 sys/dev/usb/usbdevs: revision 1.775 sys/arch/arm/rockchip/rk_dwhdmi.c: revision 1.4 sys/dev/fdt/simple_amplifier.c: revision 1.1 sys/dev/i2c/files.i2c: revision 1.105 sys/arch/evbarm/conf/GENERIC64: revision 1.117 sys/arch/evbarm/conf/GENERIC64: revision 1.118 sys/dev/i2c/files.i2c: revision 1.107 sys/dev/fdt/files.fdt: revision 1.49 sys/arch/arm/rockchip/rk_anxdp.c: revision 1.1 sys/dev/ic/dwc_mmc_var.h: revision 1.9 sys/dev/i2c/rkpmic.c: revision 1.4 sys/arch/arm/rockchip/rk_anxdp.c: revision 1.2 sys/dev/i2c/rkpmic.c: revision 1.5 sys/dev/i2c/rkpmic.c: revision 1.6 sys/arch/arm/rockchip/rk_vop.c: revision 1.4 sys/arch/arm/rockchip/rk_vop.c: revision 1.5 sys/dev/i2c/rkpmic.c: revision 1.8 sys/arch/arm/dts/rk3399-pinebook-pro.dts: revision 1.1 sys/dev/usb/ums.c: revision 1.96 (via patch) sys/arch/arm/rockchip/rk_pwm.c: revision 1.3 sys/arch/arm/dts/rk3399-pinebook-pro.dts: revision 1.2 sys/dev/i2c/es8316ac.c: revision 1.1 sys/dev/fdt/dwcmmc_fdt.c: revision 1.10 sys/dev/i2c/es8316ac.c: revision 1.2 sys/dev/fdt/fdt_panel.c: revision 1.1 sys/dev/ic/dwc_mmc.c: revision 1.18 sys/dev/fdt/fdt_panel.c: revision 1.2 sys/dev/ic/dwc_mmc.c: revision 1.19 sys/arch/arm/dts/rk3399-rockpro64.dts: revision 1.10 sys/dev/fdt/fdt_port.c: revision 1.5 sys/dev/ic/dwc_mmc_var.h: revision 1.10 sys/dev/fdt/fdt_port.c: revision 1.6 sys/arch/evbarm/conf/GENERIC64: revision 1.122 sys/dev/ic/dwc_mmc_var.h: revision 1.11 sys/dev/fdt/files.fdt: revision 1.50 sys/arch/evbarm/conf/GENERIC64: revision 1.123 sys/arch/arm/rockchip/rk_i2s.c: revision 1.2 sys/arch/arm/rockchip/files.rockchip: revision 1.23 sys/arch/evbarm/conf/GENERIC64: revision 1.124 sys/dev/ic/anx_dp.c: revision 1.1 rkpmic: add RTC support; register w/ todr(9) rkpmic: correct delay Add support for SDIO interrupts. fix copy/paste error in mux_pll_src_cpll_gpll_ppll_parents[] add RK3399 eDP clocks add RK3399 DisplayPort clocks style fix/KNF rk3399_cru: implement dclk_vop0_frac and dclk_vop1_frac Move drm_encoder from rkvop(4) to the SoC-layer output pipe drivers (rk_dwhdmi). rkvop: set stride using virtual framebuffer width instead of display mode rk3399_cru: Reparent dclk_vop[01] to gpll via dclk_vop[01]_frac. The previous source of dclk_vop[01] was vpll via dclk_vop[01]_div. vpll is apparently used directly as a pixel clock source for the HDMI PHY, and we don't want the other VOP's dclk changing out from under it because we can't handle finding a replacement clock source with the right rate yet. gpll happens to run at 594MHz, which works well as a basis for pixel clocks. Linux suggests that the source clock of the fractional divider needs to be more than twenty times greater than the resulting clock (or some intermediate clock?) for output stability. This may not be the case with 594MHz and the common pixel clocks I see used by displays in my area of the wild, but it works for now. add Analogix DisplayPort core driver add Rockchip (RK3399) glue for Analogix DisplayPort core add anxdp(4) Add another panel@fdt driver, this time for DRM-style panels. To do: migrate away from other panel driver. enable panel at fdt drivers paper over the rkpwm get_conf function that otherwise doesn't seem to let things work add template bits for optional eDP panel on RockPro64 Abort panel driver attach if required regulator is missing. Add clk provider Add Pinebook Pro dts, from Manjaro Linux. https://gitlab.manjaro.org/tsys/linux-pinebook-pro/blob/877ca0e7283596f37845de50dc36bff5b88b91e1/arch/arm64/boot/dts/rockchip/rk3399-pinebook-pro.dts + rk3399-pinebook-pro.dts Attach mmcpwrseq resource earlier dwcmmc improvements: - Use mmcpwrseq resources if available - Only set 4- or 8-bit mode if specified in the dt properties - Add quirk for implementations with inverted power enable logic - Support switching signal voltage between 1.8V and 3.3V - Fix a clock divider issue on Rockchip SoCs Fix performance regression with previous Quiet chatty printfs No need to print all supported levels at attach, print the range and total number of steps Disable SPI for now (rkspi driver hangs at boot) Add driver for simple-audio-amplifier binding Add driver for Everest Semi ES8316 Low Power Audio CODEC add es8316, simpleamp Avoid sleeping while the audio intr lock is held. If the rockchip,system-power-controller property is present, try to power off with the PMIC Add HAILUCK keyboard (product 1e) Add a quirk for the HAILUCK USB keyboard / touchpad device with product 1e. The keyboard does not function properly unless the touchpad's intr endpoint is active. Add driver for CellWise CW2015 Fuel Gauge IC. add cwfg Emit PMFE_DISPLAY_{ON,OFF} events in response to DPMS requests. If the backlight node does not have an enable gpio, set the lowest duty cycle to turn the display off instead. Attach psci as early as possible. This allows other power controllers to register their own poweroff / reset callbacks with a higher preference. Add 2000 MHz to available armclkb rates Remove debug printfs
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/fdt/dwcmmc_fdt.c80
-rw-r--r--sys/dev/fdt/fdt_panel.c167
-rw-r--r--sys/dev/fdt/fdt_port.c16
-rw-r--r--sys/dev/fdt/files.fdt11
-rw-r--r--sys/dev/fdt/pwm_backlight.c37
-rw-r--r--sys/dev/fdt/simple_amplifier.c137
-rw-r--r--sys/dev/i2c/cwfg.c449
-rw-r--r--sys/dev/i2c/es8316ac.c722
-rw-r--r--sys/dev/i2c/files.i2c12
-rw-r--r--sys/dev/i2c/rkpmic.c263
-rw-r--r--sys/dev/ic/anx_dp.c1079
-rw-r--r--sys/dev/ic/anx_dp.h79
-rw-r--r--sys/dev/ic/dwc_mmc.c502
-rw-r--r--sys/dev/ic/dwc_mmc_var.h23
-rw-r--r--sys/dev/usb/ums.c58
-rw-r--r--sys/dev/usb/usbdevs6
16 files changed, 3365 insertions, 276 deletions
diff --git a/sys/dev/fdt/dwcmmc_fdt.c b/sys/dev/fdt/dwcmmc_fdt.c
index 0d5abc21e08..5c68cb3d9ca 100644
--- a/sys/dev/fdt/dwcmmc_fdt.c
+++ b/sys/dev/fdt/dwcmmc_fdt.c
@@ -1,4 +1,4 @@
-/* $NetBSD: dwcmmc_fdt.c,v 1.8 2019/04/30 23:10:33 jmcneill Exp $ */
+/* $NetBSD: dwcmmc_fdt.c,v 1.8.2.1 2020/01/21 10:39:58 martin Exp $ */
/*-
* Copyright (c) 2015-2018 Jared McNeill <jmcneill@invisible.ca>
@@ -27,7 +27,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: dwcmmc_fdt.c,v 1.8 2019/04/30 23:10:33 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: dwcmmc_fdt.c,v 1.8.2.1 2020/01/21 10:39:58 martin Exp $");
#include <sys/param.h>
#include <sys/bus.h>
@@ -40,20 +40,28 @@ __KERNEL_RCSID(0, "$NetBSD: dwcmmc_fdt.c,v 1.8 2019/04/30 23:10:33 jmcneill Exp
#include <sys/gpio.h>
#include <dev/ic/dwc_mmc_var.h>
+#include <dev/sdmmc/sdmmcchip.h>
#include <dev/fdt/fdtvar.h>
static int dwcmmc_fdt_match(device_t, cfdata_t, void *);
static void dwcmmc_fdt_attach(device_t, device_t, void *);
+static void dwcmmc_fdt_pre_power_on(struct dwc_mmc_softc *);
+static void dwcmmc_fdt_post_power_on(struct dwc_mmc_softc *);
+
static int dwcmmc_fdt_card_detect(struct dwc_mmc_softc *);
static int dwcmmc_fdt_bus_clock(struct dwc_mmc_softc *, int);
+static int dwcmmc_fdt_signal_voltage(struct dwc_mmc_softc *, int);
struct dwcmmc_fdt_config {
u_int ciu_div;
+ u_int flags;
};
static const struct dwcmmc_fdt_config dwcmmc_rk3288_config = {
.ciu_div = 2,
+ .flags = DWC_MMC_F_USE_HOLD_REG |
+ DWC_MMC_F_DMA,
};
static const struct of_compat_data compat_data[] = {
@@ -68,6 +76,8 @@ struct dwcmmc_fdt_softc {
struct fdtbus_gpio_pin *sc_pin_cd;
const struct dwcmmc_fdt_config *sc_conf;
u_int sc_ciu_div;
+ struct fdtbus_regulator *sc_vqmmc;
+ struct fdtbus_mmc_pwrseq *sc_pwrseq;
};
CFATTACH_DECL_NEW(dwcmmc_fdt, sizeof(struct dwcmmc_fdt_softc),
@@ -126,6 +136,9 @@ dwcmmc_fdt_attach(device_t parent, device_t self, void *aux)
return;
}
+ esc->sc_vqmmc = fdtbus_regulator_acquire(phandle, "vqmmc-supply");
+ esc->sc_pwrseq = fdtbus_mmc_pwrseq_get(phandle);
+
sc->sc_dev = self;
sc->sc_bst = faa->faa_bst;
sc->sc_dmat = faa->faa_dmat;
@@ -137,13 +150,18 @@ dwcmmc_fdt_attach(device_t parent, device_t self, void *aux)
}
esc->sc_conf = (void *)of_search_compatible(phandle, compat_data)->data;
-
if (of_getprop_uint32(phandle, "max-frequency", &sc->sc_clock_freq) != 0)
sc->sc_clock_freq = UINT_MAX;
+ if (of_getprop_uint32(phandle, "bus-width", &sc->sc_bus_width) != 0)
+ sc->sc_bus_width = 4;
sc->sc_fifo_depth = fifo_depth;
- sc->sc_flags = DWC_MMC_F_USE_HOLD_REG | DWC_MMC_F_DMA;
+ sc->sc_ciu_div = esc->sc_conf->ciu_div;
+ sc->sc_flags = esc->sc_conf->flags;
+ sc->sc_pre_power_on = dwcmmc_fdt_pre_power_on;
+ sc->sc_post_power_on = dwcmmc_fdt_post_power_on;
sc->sc_bus_clock = dwcmmc_fdt_bus_clock;
+ sc->sc_signal_voltage = dwcmmc_fdt_signal_voltage;
esc->sc_pin_cd = fdtbus_gpio_acquire(phandle, "cd-gpios",
GPIO_PIN_INPUT);
@@ -171,6 +189,24 @@ dwcmmc_fdt_attach(device_t parent, device_t self, void *aux)
aprint_normal_dev(self, "interrupting on %s\n", intrstr);
}
+static void
+dwcmmc_fdt_pre_power_on(struct dwc_mmc_softc *sc)
+{
+ struct dwcmmc_fdt_softc *esc = device_private(sc->sc_dev);
+
+ if (esc->sc_pwrseq != NULL)
+ fdtbus_mmc_pwrseq_pre_power_on(esc->sc_pwrseq);
+}
+
+static void
+dwcmmc_fdt_post_power_on(struct dwc_mmc_softc *sc)
+{
+ struct dwcmmc_fdt_softc *esc = device_private(sc->sc_dev);
+
+ if (esc->sc_pwrseq != NULL)
+ fdtbus_mmc_pwrseq_post_power_on(esc->sc_pwrseq);
+}
+
static int
dwcmmc_fdt_card_detect(struct dwc_mmc_softc *sc)
{
@@ -185,7 +221,7 @@ static int
dwcmmc_fdt_bus_clock(struct dwc_mmc_softc *sc, int rate)
{
struct dwcmmc_fdt_softc *esc = device_private(sc->sc_dev);
- const u_int ciu_div = esc->sc_conf->ciu_div > 0 ? esc->sc_conf->ciu_div : 1;
+ const u_int ciu_div = sc->sc_ciu_div > 0 ? sc->sc_ciu_div : 1;
int error;
error = clk_set_rate(esc->sc_clk_ciu, 1000 * rate * ciu_div);
@@ -195,10 +231,42 @@ dwcmmc_fdt_bus_clock(struct dwc_mmc_softc *sc, int rate)
return error;
}
- sc->sc_clock_freq = clk_get_rate(esc->sc_clk_ciu) / ciu_div;
+ sc->sc_clock_freq = clk_get_rate(esc->sc_clk_ciu);
aprint_debug_dev(sc->sc_dev, "set clock rate to %u kHz (target %u kHz)\n",
sc->sc_clock_freq, rate);
return 0;
}
+
+static int
+dwcmmc_fdt_signal_voltage(struct dwc_mmc_softc *sc, int signal_voltage)
+{
+ struct dwcmmc_fdt_softc *esc = device_private(sc->sc_dev);
+ u_int uvol;
+ int error;
+
+ if (esc->sc_vqmmc == NULL)
+ return 0;
+
+ switch (signal_voltage) {
+ case SDMMC_SIGNAL_VOLTAGE_180:
+ uvol = 1800000;
+ break;
+ case SDMMC_SIGNAL_VOLTAGE_330:
+ uvol = 3300000;
+ break;
+ default:
+ return EINVAL;
+ }
+
+ error = fdtbus_regulator_supports_voltage(esc->sc_vqmmc, uvol, uvol);
+ if (error != 0)
+ return 0;
+
+ error = fdtbus_regulator_set_voltage(esc->sc_vqmmc, uvol, uvol);
+ if (error != 0)
+ return error;
+
+ return fdtbus_regulator_enable(esc->sc_vqmmc);
+}
diff --git a/sys/dev/fdt/fdt_panel.c b/sys/dev/fdt/fdt_panel.c
new file mode 100644
index 00000000000..db977b86def
--- /dev/null
+++ b/sys/dev/fdt/fdt_panel.c
@@ -0,0 +1,167 @@
+/* $NetBSD: fdt_panel.c,v 1.2.4.2 2020/01/21 10:39:58 martin Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jonathan A. Kollasch <jakllsch@kollasch.net>
+ * 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 ``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 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: fdt_panel.c,v 1.2.4.2 2020/01/21 10:39:58 martin Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/systm.h>
+#include <sys/gpio.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/fdt_port.h>
+
+#include <dev/i2c/ddcvar.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_edid.h>
+
+static const char * const compatible[] = {
+ "simple-panel",
+ NULL
+};
+
+struct panel_fdt_softc {
+ device_t sc_dev;
+ struct fdt_device_ports sc_ports;
+ struct drm_panel sc_panel;
+ struct fdtbus_gpio_pin * sc_enable;
+ struct fdtbus_regulator * sc_regulator;
+};
+
+#define to_panel_fdt(x) container_of(x, struct panel_fdt_softc, sc_panel)
+
+static int
+panel_fdt_enable(struct drm_panel * panel)
+{
+ struct panel_fdt_softc * const sc = to_panel_fdt(panel);
+
+ if (sc->sc_enable) {
+ fdtbus_gpio_write(sc->sc_enable, true);
+ }
+
+ if (!cold)
+ kpause("panele", false, mstohz(200), NULL);
+
+ return 0;
+}
+
+static int
+panel_fdt_prepare(struct drm_panel * panel)
+{
+ struct panel_fdt_softc * const sc = to_panel_fdt(panel);
+
+ if (sc->sc_regulator) {
+ fdtbus_regulator_enable(sc->sc_regulator);
+ }
+
+ if (cold)
+ delay(210000);
+ else
+ kpause("panelp", false, mstohz(210), NULL);
+
+ return 0;
+}
+
+static const struct drm_panel_funcs panel_fdt_funcs = {
+ .disable = NULL,
+ .enable = panel_fdt_enable,
+ .get_modes = NULL,
+ .get_timings = NULL,
+ .prepare = panel_fdt_prepare,
+ .unprepare = NULL,
+};
+
+static int
+panel_fdt_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
+{
+ struct fdt_endpoint *rep = fdt_endpoint_remote(ep);
+
+ if (fdt_endpoint_port_index(ep) != 0)
+ return EINVAL;
+
+ if (fdt_endpoint_type(rep) != EP_DRM_ENCODER)
+ return EINVAL;
+
+ return 0;
+}
+
+static void *
+panel_fdt_ep_get_data(device_t dev, struct fdt_endpoint *ep)
+{
+ struct panel_fdt_softc * const sc = device_private(dev);
+
+ return &sc->sc_panel;
+}
+
+static int
+panel_fdt_match(device_t parent, cfdata_t cf, void *aux)
+{
+ struct fdt_attach_args * const faa = aux;
+
+ return of_match_compatible(faa->faa_phandle, compatible);
+}
+
+static void
+panel_fdt_attach(device_t parent, device_t self, void *aux)
+{
+ struct panel_fdt_softc * const sc = device_private(self);
+ struct fdt_attach_args * const faa = aux;
+ const int phandle = faa->faa_phandle;
+
+ aprint_naive("\n");
+ aprint_normal(": panel\n");
+
+ sc->sc_dev = self;
+
+ /* required for "simple-panel" */
+ sc->sc_regulator = fdtbus_regulator_acquire(phandle, "power-supply");
+ if (sc->sc_regulator == NULL) {
+ aprint_error_dev(self, "regulator not found\n");
+ return;
+ }
+
+ /* optional for "simple-panel" */
+ sc->sc_enable = fdtbus_gpio_acquire_index(phandle,
+ "enable-gpios", 0, GPIO_PIN_OUTPUT);
+
+ sc->sc_ports.dp_ep_activate = panel_fdt_ep_activate;
+ sc->sc_ports.dp_ep_get_data = panel_fdt_ep_get_data;
+ fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_PANEL);
+
+ drm_panel_init(&sc->sc_panel);
+ sc->sc_panel.funcs = &panel_fdt_funcs;
+
+ drm_panel_add(&sc->sc_panel);
+}
+
+CFATTACH_DECL_NEW(panel_fdt, sizeof(struct panel_fdt_softc),
+ panel_fdt_match, panel_fdt_attach, NULL, NULL);
diff --git a/sys/dev/fdt/fdt_port.c b/sys/dev/fdt/fdt_port.c
index 7f7cafa6716..f24e3a35234 100644
--- a/sys/dev/fdt/fdt_port.c
+++ b/sys/dev/fdt/fdt_port.c
@@ -1,4 +1,4 @@
-/* $NetBSD: fdt_port.c,v 1.2.4.1 2019/11/25 16:18:40 martin Exp $ */
+/* $NetBSD: fdt_port.c,v 1.2.4.2 2020/01/21 10:39:58 martin Exp $ */
/*-
* Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -38,7 +38,7 @@
#include <sys/cdefs.h>
-__KERNEL_RCSID(1, "$NetBSD: fdt_port.c,v 1.2.4.1 2019/11/25 16:18:40 martin Exp $");
+__KERNEL_RCSID(1, "$NetBSD: fdt_port.c,v 1.2.4.2 2020/01/21 10:39:58 martin Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@@ -190,8 +190,8 @@ fdt_endpoint_activate(struct fdt_endpoint *ep, bool activate)
return EBUSY;
rdp = rep->ep_port->port_dp;
-device_printf(rdp->dp_dev, "activating port %d endpoint %d\n",
- fdt_endpoint_port_index(rep), fdt_endpoint_index(rep));
+ aprint_debug_dev(rdp->dp_dev, "activating port %d endpoint %d\n",
+ fdt_endpoint_port_index(rep), fdt_endpoint_index(rep));
if (rdp->dp_ep_activate)
error = rdp->dp_ep_activate(rdp->dp_dev, rep, activate);
@@ -207,8 +207,8 @@ fdt_endpoint_activate_direct(struct fdt_endpoint *ep, bool activate)
int error = 0;
dp = ep->ep_port->port_dp;
-device_printf(dp->dp_dev, "activating port %d endpoint %d (direct)\n",
- fdt_endpoint_port_index(ep), fdt_endpoint_index(ep));
+ aprint_debug_dev(dp->dp_dev, "activating port %d endpoint %d (direct)\n",
+ fdt_endpoint_port_index(ep), fdt_endpoint_index(ep));
if (dp->dp_ep_activate)
error = dp->dp_ep_activate(dp->dp_dev, ep, activate);
@@ -372,8 +372,8 @@ fdt_endpoints_register(int phandle, struct fdt_port *port,
} else if (rep != NULL) {
rep->ep_rep = ep;
rep->ep_rphandle = child;
- aprint_verbose("%s ", ep_name(ep, buf, sizeof(buf)));
- aprint_verbose("connected to %s\n",
+ aprint_debug("%s ", ep_name(ep, buf, sizeof(buf)));
+ aprint_debug("connected to %s\n",
ep_name(rep, buf, sizeof(buf)));
if (rep->ep_type == EP_OTHER)
rep->ep_type = ep->ep_type;
diff --git a/sys/dev/fdt/files.fdt b/sys/dev/fdt/files.fdt
index 4e47dcb8ce8..39b3daccee0 100644
--- a/sys/dev/fdt/files.fdt
+++ b/sys/dev/fdt/files.fdt
@@ -1,4 +1,4 @@
-# $NetBSD: files.fdt,v 1.44.4.2 2019/11/27 13:46:45 martin Exp $
+# $NetBSD: files.fdt,v 1.44.4.3 2020/01/21 10:39:58 martin Exp $
include "external/bsd/libfdt/conf/files.libfdt"
@@ -45,6 +45,9 @@ device panel: fdt_port
attach panel at fdt with fdt_panel
file dev/fdt/panel_fdt.c fdt_panel
+attach panel at fdt with panel_fdt: drmkms
+file dev/fdt/fdt_panel.c panel_fdt
+
device dispcon: fdt_port, drmkms, ddc_read_edid
attach dispcon at fdt with dispcon_hdmi
file dev/fdt/hdmi_connector.c dispcon_hdmi
@@ -161,3 +164,9 @@ file dev/fdt/arasan_sdhc_fdt.c arasan_sdhc_fdt
device usbnopphy
attach usbnopphy at fdt
file dev/fdt/usbnopphy.c usbnopphy
+
+# Simple Amplifier Audio Driver
+device simpleamp
+attach simpleamp at fdt
+file dev/fdt/simple_amplifier.c simpleamp
+
diff --git a/sys/dev/fdt/pwm_backlight.c b/sys/dev/fdt/pwm_backlight.c
index b9832898b41..7b46fe4084a 100644
--- a/sys/dev/fdt/pwm_backlight.c
+++ b/sys/dev/fdt/pwm_backlight.c
@@ -1,4 +1,4 @@
-/* $NetBSD: pwm_backlight.c,v 1.4 2018/05/10 13:11:21 jmcneill Exp $ */
+/* $NetBSD: pwm_backlight.c,v 1.4.10.1 2020/01/21 10:39:58 martin Exp $ */
/*-
* Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca>
@@ -27,7 +27,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: pwm_backlight.c,v 1.4 2018/05/10 13:11:21 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: pwm_backlight.c,v 1.4.10.1 2020/01/21 10:39:58 martin Exp $");
#include <sys/param.h>
#include <sys/bus.h>
@@ -48,6 +48,7 @@ struct pwm_backlight_softc {
u_int *sc_levels;
u_int sc_nlevels;
+ u_int sc_curlevel;
char *sc_levelstr;
@@ -117,11 +118,11 @@ pwm_backlight_attach(device_t parent, device_t self, void *aux)
aprint_naive("\n");
aprint_normal(": PWM Backlight");
- aprint_verbose(" <");
- for (n = 0; n < sc->sc_nlevels; n++) {
- aprint_verbose("%s%u", n ? " " : "", sc->sc_levels[n]);
+ if (sc->sc_nlevels > 1) {
+ aprint_normal(", %u-%u (%u steps)",
+ sc->sc_levels[0], sc->sc_levels[sc->sc_nlevels - 1],
+ sc->sc_nlevels);
}
- aprint_verbose(">");
aprint_normal("\n");
sc->sc_lid_state = true;
@@ -131,6 +132,8 @@ pwm_backlight_attach(device_t parent, device_t self, void *aux)
pwm_backlight_set(sc, default_level);
}
+ sc->sc_curlevel = pwm_backlight_get(sc);
+
pwm_backlight_sysctl_init(sc);
pwm_backlight_pmf_init(sc);
}
@@ -247,12 +250,21 @@ failed:
}
static void
+pwm_backlight_enable(struct pwm_backlight_softc *sc, int enable)
+{
+ if (sc->sc_pin)
+ fdtbus_gpio_write(sc->sc_pin, enable);
+ else
+ pwm_backlight_set(sc, enable ? sc->sc_curlevel : 0);
+}
+
+static void
pwm_backlight_display_on(device_t dev)
{
struct pwm_backlight_softc * const sc = device_private(dev);
- if (sc->sc_pin && sc->sc_lid_state)
- fdtbus_gpio_write(sc->sc_pin, 1);
+ if (sc->sc_lid_state)
+ pwm_backlight_enable(sc, 1);
}
static void
@@ -260,8 +272,7 @@ pwm_backlight_display_off(device_t dev)
{
struct pwm_backlight_softc * const sc = device_private(dev);
- if (sc->sc_pin)
- fdtbus_gpio_write(sc->sc_pin, 0);
+ pwm_backlight_enable(sc, 0);
}
static void
@@ -271,8 +282,7 @@ pwm_backlight_chassis_lid_open(device_t dev)
sc->sc_lid_state = true;
- if (sc->sc_pin)
- fdtbus_gpio_write(sc->sc_pin, 1);
+ pwm_backlight_enable(sc, 1);
}
static void
@@ -282,8 +292,7 @@ pwm_backlight_chassis_lid_close(device_t dev)
sc->sc_lid_state = false;
- if (sc->sc_pin)
- fdtbus_gpio_write(sc->sc_pin, 0);
+ pwm_backlight_enable(sc, 0);
}
static void
diff --git a/sys/dev/fdt/simple_amplifier.c b/sys/dev/fdt/simple_amplifier.c
new file mode 100644
index 00000000000..0207e9dcaa6
--- /dev/null
+++ b/sys/dev/fdt/simple_amplifier.c
@@ -0,0 +1,137 @@
+/* $NetBSD: simple_amplifier.c,v 1.1.4.2 2020/01/21 10:39:58 martin Exp $ */
+
+/*-
+ * Copyright (c) 2020 Jared McNeill <jmcneill@invisible.ca>
+ * 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 ``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 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: simple_amplifier.c,v 1.1.4.2 2020/01/21 10:39:58 martin Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/systm.h>
+#include <sys/sysctl.h>
+#include <sys/kmem.h>
+
+#include <dev/audio/audio_dai.h>
+
+#include <dev/fdt/fdtvar.h>
+
+struct simple_amplifier_softc {
+ device_t sc_dev;
+ struct audio_dai_device sc_dai;
+ struct fdtbus_gpio_pin *sc_pin;
+ struct fdtbus_regulator *sc_vcc;
+};
+
+static int simple_amplifier_match(device_t, cfdata_t, void *);
+static void simple_amplifier_attach(device_t, device_t, void *);
+
+static const char *compatible[] = {
+ "simple-audio-amplifier",
+ NULL
+};
+
+CFATTACH_DECL_NEW(simpleamp, sizeof(struct simple_amplifier_softc),
+ simple_amplifier_match, simple_amplifier_attach, NULL, NULL);
+
+static int
+simple_amplifier_open(void *priv, int flags)
+{
+ struct simple_amplifier_softc * const sc = priv;
+ int error;
+
+ if (sc->sc_pin != NULL)
+ fdtbus_gpio_write(sc->sc_pin, 1);
+
+ if (sc->sc_vcc != NULL) {
+ error = fdtbus_regulator_enable(sc->sc_vcc);
+ if (error != 0)
+ aprint_error_dev(sc->sc_dev, "couldn't enable power supply\n");
+ }
+
+ return 0;
+}
+
+static void
+simple_amplifier_close(void *priv)
+{
+ struct simple_amplifier_softc * const sc = priv;
+
+ if (sc->sc_pin != NULL)
+ fdtbus_gpio_write(sc->sc_pin, 0);
+
+ if (sc->sc_vcc != NULL)
+ (void)fdtbus_regulator_disable(sc->sc_vcc);
+}
+
+static const struct audio_hw_if simple_amplifier_hw_if = {
+ .open = simple_amplifier_open,
+ .close = simple_amplifier_close,
+};
+
+static audio_dai_tag_t
+simple_amplifier_dai_get_tag(device_t dev, const void *data, size_t len)
+{
+ struct simple_amplifier_softc * const sc = device_private(dev);
+
+ if (len != 4)
+ return NULL;
+
+ return &sc->sc_dai;
+}
+
+static struct fdtbus_dai_controller_func simple_amplifier_dai_funcs = {
+ .get_tag = simple_amplifier_dai_get_tag
+};
+
+static int
+simple_amplifier_match(device_t parent, cfdata_t cf, void *aux)
+{
+ struct fdt_attach_args * const faa = aux;
+
+ return of_match_compatible(faa->faa_phandle, compatible);
+}
+
+static void
+simple_amplifier_attach(device_t parent, device_t self, void *aux)
+{
+ struct simple_amplifier_softc * const sc = device_private(self);
+ struct fdt_attach_args * const faa = aux;
+ const int phandle = faa->faa_phandle;
+
+ sc->sc_dev = self;
+ sc->sc_pin = fdtbus_gpio_acquire(phandle, "enable-gpios", GPIO_PIN_OUTPUT);
+ sc->sc_vcc = fdtbus_regulator_acquire(phandle, "VCC-supply");
+
+ aprint_naive("\n");
+ aprint_normal(": Simple Amplifier\n");
+
+ sc->sc_dai.dai_hw_if = &simple_amplifier_hw_if;
+ sc->sc_dai.dai_dev = self;
+ sc->sc_dai.dai_priv = sc;
+ fdtbus_register_dai_controller(self, phandle, &simple_amplifier_dai_funcs);
+}
diff --git a/sys/dev/i2c/cwfg.c b/sys/dev/i2c/cwfg.c
new file mode 100644
index 00000000000..cd9aba69141
--- /dev/null
+++ b/sys/dev/i2c/cwfg.c
@@ -0,0 +1,449 @@
+/* $NetBSD: cwfg.c,v 1.1.4.2 2020/01/21 10:39:58 martin Exp $ */
+
+/*-
+ * Copyright (c) 2020 Jared McNeill <jmcneill@invisible.ca>
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: cwfg.c,v 1.1.4.2 2020/01/21 10:39:58 martin Exp $");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/device.h>
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/kmem.h>
+
+#include <dev/i2c/i2cvar.h>
+
+#include <dev/sysmon/sysmonvar.h>
+#include <dev/sysmon/sysmon_taskq.h>
+
+#include <dev/fdt/fdtvar.h>
+
+#define VERSION_REG 0x00
+#define VCELL_HI_REG 0x02
+#define VCELL_HI __BITS(5,0)
+#define VCELL_LO_REG 0x03
+#define VCELL_LO __BITS(7,0)
+#define SOC_HI_REG 0x04
+#define SOC_LO_REG 0x05
+#define RTT_ALRT_HI_REG 0x06
+#define RTT_ALRT __BIT(7)
+#define RTT_HI __BITS(4,0)
+#define RTT_ALRT_LO_REG 0x07
+#define RTT_LO __BITS(7,0)
+#define CONFIG_REG 0x08
+#define CONFIG_ATHD __BITS(7,3)
+#define CONFIG_UFG __BIT(1)
+#define MODE_REG 0x0a
+#define MODE_SLEEP __BITS(7,6)
+#define MODE_SLEEP_WAKE 0x0
+#define MODE_SLEEP_SLEEP 0x3
+#define MODE_QSTRT __BITS(5,4)
+#define MODE_POR __BITS(3,0)
+#define BATINFO_REG(n) (0x10 + (n))
+
+#define VCELL_STEP 312
+#define VCELL_DIV 1024
+#define BATINFO_SIZE 64
+#define RESET_COUNT 30
+#define RESET_DELAY 100000
+
+enum cwfg_sensor {
+ CWFG_SENSOR_VCELL,
+ CWFG_SENSOR_SOC,
+ CWFG_SENSOR_RTT,
+ CWFG_NSENSORS
+};
+
+struct cwfg_softc {
+ device_t sc_dev;
+ i2c_tag_t sc_i2c;
+ i2c_addr_t sc_addr;
+ int sc_phandle;
+
+ uint8_t sc_batinfo[BATINFO_SIZE];
+
+ u_int sc_alert_level;
+ u_int sc_monitor_interval;
+ u_int sc_design_capacity;
+
+ struct sysmon_envsys *sc_sme;
+
+ envsys_data_t sc_sensor[CWFG_NSENSORS];
+};
+
+#define CWFG_MONITOR_INTERVAL_DEFAULT 8
+#define CWFG_DESIGN_CAPACITY_DEFAULT 2000
+#define CWFG_ALERT_LEVEL_DEFAULT 0
+
+static const struct device_compatible_entry compat_data[] = {
+ { "cellwise,cw201x", 1 },
+ { NULL, 0 }
+};
+
+static int
+cwfg_lock(struct cwfg_softc *sc)
+{
+ return iic_acquire_bus(sc->sc_i2c, 0);
+}
+
+static void
+cwfg_unlock(struct cwfg_softc *sc)
+{
+ iic_release_bus(sc->sc_i2c, 0);
+}
+
+static int
+cwfg_read(struct cwfg_softc *sc, uint8_t reg, uint8_t *val)
+{
+ return iic_smbus_read_byte(sc->sc_i2c, sc->sc_addr, reg, val, 0);
+}
+
+static int
+cwfg_write(struct cwfg_softc *sc, uint8_t reg, uint8_t val)
+{
+ return iic_smbus_write_byte(sc->sc_i2c, sc->sc_addr, reg, val, 0);
+}
+
+static void
+cwfg_sensor_refresh(struct sysmon_envsys *sme, envsys_data_t *e)
+{
+ struct cwfg_softc *sc = sme->sme_cookie;
+ u_int vcell, rtt, tmp;
+ uint8_t val;
+ int error, n;
+
+ e->state = ENVSYS_SINVALID;
+
+ if ((error = cwfg_lock(sc)) != 0)
+ return;
+
+ switch (e->private) {
+ case CWFG_SENSOR_VCELL:
+ /* Take the average of three readings */
+ vcell = 0;
+ for (n = 0; n < 3; n++) {
+ if ((error = cwfg_read(sc, VCELL_HI_REG, &val)) != 0)
+ goto done;
+ tmp = __SHIFTOUT(val, VCELL_HI) << 8;
+ if ((error = cwfg_read(sc, VCELL_LO_REG, &val)) != 0)
+ goto done;
+ tmp |= __SHIFTOUT(val, VCELL_LO);
+ vcell += tmp;
+ }
+ vcell /= 3;
+
+ e->state = ENVSYS_SVALID;
+ e->value_cur = ((vcell * VCELL_STEP) / VCELL_DIV) * 1000;
+ break;
+
+ case CWFG_SENSOR_SOC:
+ if ((error = cwfg_read(sc, SOC_HI_REG, &val)) != 0)
+ goto done;
+
+ if (val != 0xff) {
+ e->state = ENVSYS_SVALID;
+ e->value_cur = val; /* batt % */
+ }
+ break;
+
+ case CWFG_SENSOR_RTT:
+ if ((error = cwfg_read(sc, RTT_ALRT_HI_REG, &val)) != 0)
+ goto done;
+ rtt = __SHIFTOUT(val, RTT_HI) << 8;
+ if ((error = cwfg_read(sc, RTT_ALRT_LO_REG, &val)) != 0)
+ goto done;
+ rtt |= __SHIFTOUT(val, RTT_LO);
+
+ if (rtt != 0x1fff) {
+ e->state = ENVSYS_SVALID;
+ e->value_cur = rtt; /* minutes */
+ }
+ break;
+ }
+
+done:
+ cwfg_unlock(sc);
+}
+
+static void
+cwfg_attach_battery(struct cwfg_softc *sc)
+{
+ envsys_data_t *e;
+
+ /* Cell voltage */
+ e = &sc->sc_sensor[CWFG_SENSOR_VCELL];
+ e->private = CWFG_SENSOR_VCELL;
+ e->units = ENVSYS_SVOLTS_DC;
+ e->state = ENVSYS_SINVALID;
+ strlcpy(e->desc, "battery voltage", sizeof(e->desc));
+ sysmon_envsys_sensor_attach(sc->sc_sme, e);
+
+ /* State of charge */
+ e = &sc->sc_sensor[CWFG_SENSOR_SOC];
+ e->private = CWFG_SENSOR_SOC;
+ e->units = ENVSYS_INTEGER;
+ e->state = ENVSYS_SINVALID;
+ e->flags = ENVSYS_FPERCENT;
+ strlcpy(e->desc, "battery percent", sizeof(e->desc));
+ sysmon_envsys_sensor_attach(sc->sc_sme, e);
+
+ /* Remaining run time */
+ e = &sc->sc_sensor[CWFG_SENSOR_RTT];
+ e->private = CWFG_SENSOR_RTT;
+ e->units = ENVSYS_INTEGER;
+ e->state = ENVSYS_SINVALID;
+ strlcpy(e->desc, "battery remaining minutes", sizeof(e->desc));
+ sysmon_envsys_sensor_attach(sc->sc_sme, e);
+}
+
+static void
+cwfg_attach_sensors(struct cwfg_softc *sc)
+{
+ sc->sc_sme = sysmon_envsys_create();
+ sc->sc_sme->sme_name = device_xname(sc->sc_dev);
+ sc->sc_sme->sme_cookie = sc;
+ sc->sc_sme->sme_refresh = cwfg_sensor_refresh;
+ sc->sc_sme->sme_events_timeout = sc->sc_monitor_interval;
+ sc->sc_sme->sme_class = SME_CLASS_BATTERY;
+ sc->sc_sme->sme_flags = SME_INIT_REFRESH;
+
+ cwfg_attach_battery(sc);
+
+ sysmon_envsys_register(sc->sc_sme);
+}
+
+static int
+cwfg_set_config(struct cwfg_softc *sc)
+{
+ u_int alert_level;
+ bool need_update;
+ uint8_t config, mode, val;
+ int error, n;
+
+ /* Read current config */
+ if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0)
+ return error;
+
+ /* Update alert level, if necessary */
+ alert_level = __SHIFTOUT(config, CONFIG_ATHD);
+ if (alert_level != sc->sc_alert_level) {
+ config &= ~CONFIG_ATHD;
+ config |= __SHIFTIN(sc->sc_alert_level, CONFIG_ATHD);
+ if ((error = cwfg_write(sc, CONFIG_REG, config)) != 0)
+ return error;
+ }
+
+ /* Re-read current config */
+ if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0)
+ return error;
+
+ /*
+ * We need to upload a battery profile if either the UFG flag
+ * is unset, or the current battery profile differs from the
+ * one in the DT.
+ */
+ need_update = (config & CONFIG_UFG) == 0;
+ if (need_update == false) {
+ for (n = 0; n < BATINFO_SIZE; n++) {
+ if ((error = cwfg_read(sc, BATINFO_REG(n), &val)) != 0)
+ return error;
+ if (sc->sc_batinfo[n] != val) {
+ need_update = true;
+ break;
+ }
+ }
+ }
+ if (need_update == false)
+ return 0;
+
+ aprint_verbose_dev(sc->sc_dev, "updating battery profile\n");
+
+ /* Update battery profile */
+ for (n = 0; n < BATINFO_SIZE; n++) {
+ val = sc->sc_batinfo[n];
+ if ((error = cwfg_write(sc, BATINFO_REG(n), val)) != 0)
+ return error;
+ }
+
+ /* Set UFG flag to switch to new profile */
+ if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0)
+ return error;
+ config |= CONFIG_UFG;
+ if ((error = cwfg_write(sc, CONFIG_REG, config)) != 0)
+ return error;
+
+ /* Restart the IC with new profile */
+ if ((error = cwfg_read(sc, MODE_REG, &mode)) != 0)
+ return error;
+ mode |= MODE_POR;
+ if ((error = cwfg_write(sc, MODE_REG, mode)) != 0)
+ return error;
+ delay(20000);
+ mode &= ~MODE_POR;
+ if ((error = cwfg_write(sc, MODE_REG, mode)) != 0)
+ return error;
+
+ return error;
+}
+
+static int
+cwfg_init(struct cwfg_softc *sc)
+{
+ uint8_t mode, soc;
+ int error, retry;
+
+ cwfg_lock(sc);
+
+ /* If the device is in sleep mode, wake it up */
+ if ((error = cwfg_read(sc, MODE_REG, &mode)) != 0)
+ goto done;
+ if (__SHIFTOUT(mode, MODE_SLEEP) == MODE_SLEEP_SLEEP) {
+ mode &= ~MODE_SLEEP;
+ mode |= __SHIFTIN(MODE_SLEEP_WAKE, MODE_SLEEP);
+ if ((error = cwfg_write(sc, MODE_REG, mode)) != 0)
+ goto done;
+ }
+
+ /* Load battery profile */
+ if ((error = cwfg_set_config(sc)) != 0)
+ goto done;
+
+ /* Wait for chip to become ready */
+ for (retry = RESET_COUNT; retry > 0; retry--) {
+ if ((error = cwfg_read(sc, SOC_HI_REG, &soc)) != 0)
+ goto done;
+ if (soc != 0xff)
+ break;
+ delay(RESET_DELAY);
+ }
+ if (retry == 0) {
+ aprint_error_dev(sc->sc_dev,
+ "WARNING: timeout waiting for chip ready\n");
+ }
+
+done:
+ cwfg_unlock(sc);
+
+ return error;
+}
+
+static int
+cwfg_parse_resources(struct cwfg_softc *sc)
+{
+ const u_int *batinfo;
+ int len = 0, n;
+
+ batinfo = fdtbus_get_prop(sc->sc_phandle,
+ "cellwise,bat-config-info", &len);
+ switch (len) {
+ case BATINFO_SIZE:
+ memcpy(sc->sc_batinfo, batinfo, BATINFO_SIZE);
+ break;
+ case BATINFO_SIZE * 4:
+ for (n = 0; n < BATINFO_SIZE; n++)
+ sc->sc_batinfo[n] = be32toh(batinfo[n]);
+ break;
+ default:
+ aprint_error_dev(sc->sc_dev,
+ "missing or invalid battery info\n");
+ return EINVAL;
+ }
+
+ if (of_getprop_uint32(sc->sc_phandle,
+ "cellwise,monitor-interval", &sc->sc_monitor_interval) != 0) {
+ sc->sc_monitor_interval = CWFG_MONITOR_INTERVAL_DEFAULT;
+ }
+
+ if (of_getprop_uint32(sc->sc_phandle,
+ "cellwise,design-capacity", &sc->sc_design_capacity) != 0) {
+ sc->sc_design_capacity = CWFG_DESIGN_CAPACITY_DEFAULT;
+ }
+
+ if (of_getprop_uint32(sc->sc_phandle,
+ "cellwise,alert-level", &sc->sc_alert_level) != 0) {
+ sc->sc_alert_level = CWFG_ALERT_LEVEL_DEFAULT;
+ }
+
+ return 0;
+}
+
+static int
+cwfg_match(device_t parent, cfdata_t match, void *aux)
+{
+ struct i2c_attach_args *ia = aux;
+ int match_result;
+
+ if (iic_use_direct_match(ia, match, compat_data, &match_result))
+ return match_result;
+
+ /* This device is direct-config only. */
+
+ return 0;
+}
+
+static void
+cwfg_attach(device_t parent, device_t self, void *aux)
+{
+ struct cwfg_softc *sc = device_private(self);
+ struct i2c_attach_args *ia = aux;
+ uint8_t ver;
+ int error;
+
+ sc->sc_dev = self;
+ sc->sc_i2c = ia->ia_tag;
+ sc->sc_addr = ia->ia_addr;
+ sc->sc_phandle = ia->ia_cookie;
+
+ cwfg_lock(sc);
+ error = cwfg_read(sc, VERSION_REG, &ver);
+ cwfg_unlock(sc);
+
+ if (error != 0) {
+ aprint_error(": device not responding, error = %d\n", error);
+ return;
+ }
+
+ aprint_naive("\n");
+ aprint_normal(": CellWise CW2015 Fuel Gauge IC (ver. 0x%02x)\n", ver);
+
+ if (cwfg_parse_resources(sc) != 0) {
+ aprint_error_dev(self, "failed to parse resources\n");
+ return;
+ }
+
+ if (cwfg_init(sc) != 0) {
+ aprint_error_dev(self, "failed to initialize device\n");
+ return;
+ }
+
+ cwfg_attach_sensors(sc);
+}
+
+CFATTACH_DECL_NEW(cwfg, sizeof(struct cwfg_softc),
+ cwfg_match, cwfg_attach, NULL, NULL);
diff --git a/sys/dev/i2c/es8316ac.c b/sys/dev/i2c/es8316ac.c
new file mode 100644
index 00000000000..89a89f886e5
--- /dev/null
+++ b/sys/dev/i2c/es8316ac.c
@@ -0,0 +1,722 @@
+/* $NetBSD: es8316ac.c,v 1.2.4.2 2020/01/21 10:39:58 martin Exp $ */
+
+/*-
+ * Copyright (c) 2020 Jared McNeill <jmcneill@invisible.ca>
+ * 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 ``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 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: es8316ac.c,v 1.2.4.2 2020/01/21 10:39:58 martin Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/bitops.h>
+
+#include <dev/audio/audio_dai.h>
+
+#include <dev/i2c/i2cvar.h>
+
+#include <dev/fdt/fdtvar.h>
+
+#define ESCODEC_RESET_REG 0x00
+#define RESET_ALL __BITS(5,0)
+#define RESET_CSM_ON __BIT(7)
+#define ESCODEC_CLKMAN1_REG 0x01
+#define CLKMAN1_MCLK_ON __BIT(6)
+#define CLKMAN1_BCLK_ON __BIT(5)
+#define CLKMAN1_CLK_CP_ON __BIT(4)
+#define CLKMAN1_CLK_DAC_ON __BIT(2)
+#define CLKMAN1_ANACLK_DAC_ON __BIT(0)
+#define ESCODEC_ADC_OSR_REG 0x03
+#define ESCODEC_SD_CLK_REG 0x09
+#define SD_CLK_MSC __BIT(7)
+#define SD_CLK_BCLK_INV __BIT(5)
+#define ESCODEC_SD_ADC_REG 0x0a
+#define ESCODEC_SD_DAC_REG 0x0b
+#define SD_FMT_LRP __BIT(5)
+#define SD_FMT_WL __BITS(4,2)
+#define SD_FMT_WL_16 3
+#define SD_FMT_MASK __BITS(1,0)
+#define SD_FMT_I2S 0
+#define ESCODEC_VMID_REG 0x0c
+#define ESCODEC_PDN_REG 0x0d
+#define ESCODEC_HPSEL_REG 0x13
+#define ESCODEC_HPMIXRT_REG 0x14
+#define HPMIXRT_LD2LHPMIX __BIT(7)
+#define HPMIXRT_RD2RHPMIX __BIT(3)
+#define ESCODEC_HPMIX_REG 0x15
+#define HPMIX_LHPMIX_MUTE __BIT(5)
+#define HPMIX_PDN_LHP_MIX __BIT(4)
+#define HPMIX_RHPMIX_MUTE __BIT(1)
+#define HPMIX_PDN_RHP_MIX __BIT(0)
+#define ESCODEC_HPMIXVOL_REG 0x16
+#define HPMIXVOL_LHPMIXVOL __BITS(7,4)
+#define HPMIXVOL_RHPMIXVOL __BITS(3,0)
+#define ESCODEC_HPOUTEN_REG 0x17
+#define HPOUTEN_EN_HPL __BIT(6)
+#define HPOUTEN_HPL_OUTEN __BIT(5)
+#define HPOUTEN_EN_HPR __BIT(2)
+#define HPOUTEN_HPR_OUTEN __BIT(1)
+#define ESCODEC_HPVOL_REG 0x18
+#define HPVOL_PDN_LICAL __BIT(7)
+#define HPVOL_HPLVOL __BITS(5,4)
+#define HPVOL_PDN_RICAL __BIT(3)
+#define HPVOL_HPRVOL __BITS(1,0)
+#define ESCODEC_HPPWR_REG 0x19
+#define HPPWR_PDN_CPHP __BIT(2)
+#define ESCODEC_CPPWR_REG 0x1a
+#define CPPWR_PDN_CP __BIT(5)
+#define ESCODEC_DACPWR_REG 0x2f
+#define DACPWR_PDN_DAC_L __BIT(4)
+#define DACPWR_PDN_DAC_R __BIT(0)
+#define ESCODEC_DACCTL1_REG 0x30
+#define DACCTL1_MUTE __BIT(5)
+#define ESCODEC_DACVOL_L_REG 0x33
+#define DACVOL_L_DACVOLUME __BITS(7,0)
+#define ESCODEC_DACVOL_R_REG 0x34
+#define DACVOL_R_DACVOLUME __BITS(7,0)
+
+static const struct device_compatible_entry compat_data[] = {
+ { "everest,es8316", 1 },
+ { NULL }
+};
+
+struct escodec_softc {
+ device_t sc_dev;
+ i2c_tag_t sc_i2c;
+ i2c_addr_t sc_addr;
+ int sc_phandle;
+ struct clk *sc_clk;
+
+ struct audio_dai_device sc_dai;
+ audio_dai_tag_t sc_amplifier;
+
+ uint8_t sc_swvol;
+};
+
+static void
+escodec_lock(struct escodec_softc *sc)
+{
+ iic_acquire_bus(sc->sc_i2c, 0);
+}
+
+static void
+escodec_unlock(struct escodec_softc *sc)
+{
+ iic_release_bus(sc->sc_i2c, 0);
+}
+
+static uint8_t
+escodec_read(struct escodec_softc *sc, uint8_t reg)
+{
+ uint8_t val;
+
+ if (iic_smbus_read_byte(sc->sc_i2c, sc->sc_addr, reg, &val, 0) != 0)
+ val = 0xff;
+
+ return val;
+}
+
+static void
+escodec_write(struct escodec_softc *sc, uint8_t reg, uint8_t val)
+{
+ (void)iic_smbus_write_byte(sc->sc_i2c, sc->sc_addr, reg, val, 0);
+}
+
+enum escodec_mixer_ctrl {
+ ESCODEC_OUTPUT_CLASS,
+ ESCODEC_INPUT_CLASS,
+ ESCODEC_OUTPUT_MASTER,
+ ESCODEC_INPUT_DAC,
+ ESCODEC_INPUT_DAC_MUTE,
+ ESCODEC_INPUT_HEADPHONE,
+ ESCODEC_INPUT_MIXEROUT,
+ ESCODEC_INPUT_MIXEROUT_MUTE,
+
+ ESCODEC_MIXER_CTRL_LAST
+};
+
+enum escodec_mixer_type {
+ ESCODEC_MIXER_CLASS,
+ ESCODEC_MIXER_SWVOL,
+ ESCODEC_MIXER_AMPLIFIER,
+ ESCODEC_MIXER_ATTENUATOR,
+ ESCODEC_MIXER_MUTE,
+};
+
+static const struct escodec_mixer {
+ const char * name;
+ int mixer_class;
+ int prev, next;
+ enum escodec_mixer_ctrl ctrl;
+ enum escodec_mixer_type type;
+ u_int reg[2];
+ uint8_t mask[2];
+ uint8_t maxval;
+} escodec_mixers[ESCODEC_MIXER_CTRL_LAST] = {
+ /*
+ * Mixer classes
+ */
+ [ESCODEC_OUTPUT_CLASS] = {
+ .name = AudioCoutputs,
+ .type = ESCODEC_MIXER_CLASS,
+ },
+ [ESCODEC_INPUT_CLASS] = {
+ .name = AudioCinputs,
+ .type = ESCODEC_MIXER_CLASS,
+ },
+
+ /*
+ * Software master volume
+ */
+ [ESCODEC_OUTPUT_MASTER] = {
+ .name = AudioNmaster,
+ .mixer_class = ESCODEC_OUTPUT_CLASS,
+ .prev = AUDIO_MIXER_LAST,
+ .next = AUDIO_MIXER_LAST,
+ .type = ESCODEC_MIXER_SWVOL,
+ },
+
+ /*
+ * Stereo DAC
+ */
+ [ESCODEC_INPUT_DAC] = {
+ .name = AudioNdac,
+ .mixer_class = ESCODEC_INPUT_CLASS,
+ .prev = AUDIO_MIXER_LAST,
+ .next = ESCODEC_INPUT_DAC_MUTE,
+ .type = ESCODEC_MIXER_ATTENUATOR,
+ .reg = {
+ [AUDIO_MIXER_LEVEL_LEFT] = ESCODEC_DACVOL_L_REG,
+ [AUDIO_MIXER_LEVEL_RIGHT] = ESCODEC_DACVOL_R_REG,
+ },
+ .mask = {
+ [AUDIO_MIXER_LEVEL_LEFT] = DACVOL_L_DACVOLUME,
+ [AUDIO_MIXER_LEVEL_RIGHT] = DACVOL_R_DACVOLUME,
+ },
+ .maxval = 0xc0,
+ },
+ [ESCODEC_INPUT_DAC_MUTE] = {
+ .name = AudioNmute,
+ .mixer_class = ESCODEC_INPUT_CLASS,
+ .prev = ESCODEC_INPUT_DAC,
+ .next = AUDIO_MIXER_LAST,
+ .type = ESCODEC_MIXER_MUTE,
+ .reg = {
+ [AUDIO_MIXER_LEVEL_MONO] = ESCODEC_DACCTL1_REG,
+ },
+ .mask = {
+ [AUDIO_MIXER_LEVEL_MONO] = DACCTL1_MUTE,
+ }
+ },
+
+ /*
+ * Charge Pump Headphones
+ */
+ [ESCODEC_INPUT_HEADPHONE] = {
+ .name = AudioNheadphone,
+ .mixer_class = ESCODEC_INPUT_CLASS,
+ .prev = AUDIO_MIXER_LAST,
+ .next = AUDIO_MIXER_LAST,
+ .type = ESCODEC_MIXER_ATTENUATOR,
+ .reg = {
+ [AUDIO_MIXER_LEVEL_LEFT] = ESCODEC_HPVOL_REG,
+ [AUDIO_MIXER_LEVEL_RIGHT] = ESCODEC_HPVOL_REG,
+ },
+ .mask = {
+ [AUDIO_MIXER_LEVEL_LEFT] = HPVOL_HPLVOL,
+ [AUDIO_MIXER_LEVEL_RIGHT] = HPVOL_HPRVOL,
+ }
+ },
+
+ /*
+ * Headphone mixer
+ */
+ [ESCODEC_INPUT_MIXEROUT] = {
+ .name = AudioNmixerout,
+ .mixer_class = ESCODEC_INPUT_CLASS,
+ .prev = AUDIO_MIXER_LAST,
+ .next = ESCODEC_INPUT_MIXEROUT_MUTE,
+ .type = ESCODEC_MIXER_AMPLIFIER,
+ .reg = {
+ [AUDIO_MIXER_LEVEL_LEFT] = ESCODEC_HPMIXVOL_REG,
+ [AUDIO_MIXER_LEVEL_RIGHT] = ESCODEC_HPMIXVOL_REG,
+ },
+ .mask = {
+ [AUDIO_MIXER_LEVEL_LEFT] = HPMIXVOL_LHPMIXVOL,
+ [AUDIO_MIXER_LEVEL_RIGHT] = HPMIXVOL_RHPMIXVOL
+ },
+ /*
+ * Datasheet says this field goes up to 0xb, but values
+ * above 0x4 result in noisy output in practice.
+ */
+ .maxval = 0x4,
+ },
+ [ESCODEC_INPUT_MIXEROUT_MUTE] = {
+ .name = AudioNmute,
+ .mixer_class = ESCODEC_INPUT_CLASS,
+ .prev = ESCODEC_INPUT_MIXEROUT,
+ .next = AUDIO_MIXER_LAST,
+ .type = ESCODEC_MIXER_MUTE,
+ .reg = {
+ [AUDIO_MIXER_LEVEL_MONO] = ESCODEC_HPMIX_REG,
+ },
+ .mask = {
+ [AUDIO_MIXER_LEVEL_MONO] = HPMIX_LHPMIX_MUTE | HPMIX_RHPMIX_MUTE,
+ }
+ },
+};
+
+static void
+escodec_swvol_codec(audio_filter_arg_t *arg)
+{
+ struct escodec_softc * const sc = arg->context;
+ const aint_t *src;
+ aint_t *dst;
+ u_int sample_count;
+ u_int i;
+
+ src = arg->src;
+ dst = arg->dst;
+ sample_count = arg->count * arg->srcfmt->channels;
+ for (i = 0; i < sample_count; i++) {
+ aint2_t v = (aint2_t)(*src++);
+ v = v * sc->sc_swvol / 255;
+ *dst++ = (aint_t)v;
+ }
+}
+
+static const struct escodec_mixer *
+escodec_get_mixer(u_int index)
+{
+ if (index >= ESCODEC_MIXER_CTRL_LAST)
+ return NULL;
+
+ return &escodec_mixers[index];
+}
+
+static int
+escodec_set_format(void *priv, int setmode,
+ const audio_params_t *play, const audio_params_t *rec,
+ audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
+{
+ struct escodec_softc * const sc = priv;
+
+ pfil->codec = escodec_swvol_codec;
+ pfil->context = sc;
+
+ return 0;
+}
+
+static int
+escodec_set_port(void *priv, mixer_ctrl_t *mc)
+{
+ struct escodec_softc * const sc = priv;
+ const struct escodec_mixer *mix;
+ int nvol, shift, ch;
+ uint8_t val;
+
+ if ((mix = escodec_get_mixer(mc->dev)) == NULL)
+ return ENXIO;
+
+ switch (mix->type) {
+ case ESCODEC_MIXER_SWVOL:
+ sc->sc_swvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
+ return 0;
+
+ case ESCODEC_MIXER_AMPLIFIER:
+ case ESCODEC_MIXER_ATTENUATOR:
+ escodec_lock(sc);
+ for (ch = 0; ch < 2; ch++) {
+ val = escodec_read(sc, mix->reg[ch]);
+ shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask[ch]));
+ nvol = mc->un.value.level[ch] >> shift;
+ if (mix->type == ESCODEC_MIXER_ATTENUATOR)
+ nvol = __SHIFTOUT_MASK(mix->mask[ch]) - nvol;
+ if (mix->maxval != 0 && nvol > mix->maxval)
+ nvol = mix->maxval;
+
+ val &= ~mix->mask[ch];
+ val |= __SHIFTIN(nvol, mix->mask[ch]);
+ escodec_write(sc, mix->reg[ch], val);
+ }
+ escodec_unlock(sc);
+ return 0;
+
+ case ESCODEC_MIXER_MUTE:
+ if (mc->un.ord < 0 || mc->un.ord > 1)
+ return EINVAL;
+ escodec_lock(sc);
+ val = escodec_read(sc, mix->reg[0]);
+ if (mc->un.ord)
+ val |= mix->mask[0];
+ else
+ val &= ~mix->mask[0];
+ escodec_write(sc, mix->reg[0], val);
+ escodec_unlock(sc);
+ return 0;
+
+ default:
+ return ENXIO;
+ }
+}
+
+static int
+escodec_get_port(void *priv, mixer_ctrl_t *mc)
+{
+ struct escodec_softc * const sc = priv;
+ const struct escodec_mixer *mix;
+ int nvol, shift, ch;
+ uint8_t val;
+
+ if ((mix = escodec_get_mixer(mc->dev)) == NULL)
+ return ENXIO;
+
+ switch (mix->type) {
+ case ESCODEC_MIXER_SWVOL:
+ mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = sc->sc_swvol;
+ mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = sc->sc_swvol;
+ return 0;
+
+ case ESCODEC_MIXER_AMPLIFIER:
+ case ESCODEC_MIXER_ATTENUATOR:
+ escodec_lock(sc);
+ for (ch = 0; ch < 2; ch++) {
+ val = escodec_read(sc, mix->reg[ch]);
+ shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask[ch]));
+ nvol = __SHIFTOUT(val, mix->mask[ch]);
+ if (mix->type == ESCODEC_MIXER_ATTENUATOR)
+ nvol = __SHIFTOUT_MASK(mix->mask[ch]) - nvol;
+ nvol <<= shift;
+ mc->un.value.level[ch] = nvol;
+ }
+ escodec_unlock(sc);
+ return 0;
+
+ case ESCODEC_MIXER_MUTE:
+ escodec_lock(sc);
+ val = escodec_read(sc, mix->reg[0]);
+ mc->un.ord = (val & mix->mask[0]) != 0;
+ escodec_unlock(sc);
+ return 0;
+
+ default:
+ return ENXIO;
+ }
+}
+
+static int
+escodec_query_devinfo(void *priv, mixer_devinfo_t *di)
+{
+ const struct escodec_mixer *mix;
+
+ if ((mix = escodec_get_mixer(di->index)) == NULL)
+ return ENXIO;
+
+ strcpy(di->label.name, mix->name);
+ di->mixer_class = mix->mixer_class;
+ di->next = mix->next;
+ di->prev = mix->prev;
+
+ switch (mix->type) {
+ case ESCODEC_MIXER_CLASS:
+ di->type = AUDIO_MIXER_CLASS;
+ return 0;
+
+ case ESCODEC_MIXER_SWVOL:
+ di->type = AUDIO_MIXER_VALUE;
+ di->un.v.delta = 1;
+ di->un.v.num_channels = 2;
+ strcpy(di->un.v.units.name, AudioNvolume);
+ return 0;
+
+ case ESCODEC_MIXER_AMPLIFIER:
+ case ESCODEC_MIXER_ATTENUATOR:
+ di->type = AUDIO_MIXER_VALUE;
+ di->un.v.delta =
+ 256 / (__SHIFTOUT_MASK(mix->mask[0]) + 1);
+ di->un.v.num_channels = 2;
+ strcpy(di->un.v.units.name, AudioNvolume);
+ return 0;
+
+ case ESCODEC_MIXER_MUTE:
+ di->type = AUDIO_MIXER_ENUM;
+ di->un.e.num_mem = 2;
+ strcpy(di->un.e.member[0].label.name, AudioNoff);
+ di->un.e.member[0].ord = 0;
+ strcpy(di->un.e.member[1].label.name, AudioNon);
+ di->un.e.member[1].ord = 1;
+ return 0;
+
+ default:
+ return ENXIO;
+ }
+}
+
+static const struct audio_hw_if escodec_hw_if = {
+ .set_format = escodec_set_format,
+ .set_port = escodec_set_port,
+ .get_port = escodec_get_port,
+ .query_devinfo = escodec_query_devinfo,
+};
+
+static int
+escodec_dai_set_sysclk(audio_dai_tag_t dai, u_int rate, int dir)
+{
+ struct escodec_softc * const sc = audio_dai_private(dai);
+ int error;
+
+ error = clk_set_rate(sc->sc_clk, rate);
+ if (error != 0)
+ return error;
+
+ return 0;
+}
+
+static int
+escodec_dai_set_format(audio_dai_tag_t dai, u_int format)
+{
+ struct escodec_softc * const sc = audio_dai_private(dai);
+ uint8_t sd_clk, sd_fmt, val;
+
+ const u_int fmt = __SHIFTOUT(format, AUDIO_DAI_FORMAT_MASK);
+ const u_int clk = __SHIFTOUT(format, AUDIO_DAI_CLOCK_MASK);
+ const u_int pol = __SHIFTOUT(format, AUDIO_DAI_POLARITY_MASK);
+
+ if (fmt != AUDIO_DAI_FORMAT_I2S)
+ return EINVAL;
+
+ if (clk != AUDIO_DAI_CLOCK_CBS_CFS)
+ return EINVAL;
+
+ switch (pol) {
+ case AUDIO_DAI_POLARITY_NB_NF:
+ sd_clk = 0;
+ sd_fmt = 0;
+ break;
+ case AUDIO_DAI_POLARITY_NB_IF:
+ sd_clk = 0;
+ sd_fmt = SD_FMT_LRP;
+ break;
+ case AUDIO_DAI_POLARITY_IB_NF:
+ sd_clk = SD_CLK_BCLK_INV;
+ sd_fmt = 0;
+ break;
+ case AUDIO_DAI_POLARITY_IB_IF:
+ sd_clk = SD_CLK_BCLK_INV;
+ sd_fmt = SD_FMT_LRP;
+ break;
+ }
+
+ escodec_lock(sc);
+
+ val = escodec_read(sc, ESCODEC_SD_CLK_REG);
+ val &= ~(SD_CLK_MSC|SD_CLK_BCLK_INV);
+ val |= sd_clk;
+ escodec_write(sc, ESCODEC_SD_CLK_REG, val);
+
+ val = escodec_read(sc, ESCODEC_SD_ADC_REG);
+ val &= ~SD_FMT_MASK;
+ val |= __SHIFTIN(SD_FMT_I2S, SD_FMT_MASK);
+ val &= ~SD_FMT_LRP;
+ val |= sd_fmt;
+ escodec_write(sc, ESCODEC_SD_ADC_REG, val);
+
+ val = escodec_read(sc, ESCODEC_SD_DAC_REG);
+ val &= ~SD_FMT_MASK;
+ val |= __SHIFTIN(SD_FMT_I2S, SD_FMT_MASK);
+ val &= ~SD_FMT_LRP;
+ val |= sd_fmt;
+ escodec_write(sc, ESCODEC_SD_DAC_REG, val);
+
+ val = escodec_read(sc, ESCODEC_CLKMAN1_REG);
+ val |= CLKMAN1_MCLK_ON;
+ val |= CLKMAN1_BCLK_ON;
+ val |= CLKMAN1_CLK_CP_ON;
+ val |= CLKMAN1_CLK_DAC_ON;
+ val |= CLKMAN1_ANACLK_DAC_ON;
+ escodec_write(sc, ESCODEC_CLKMAN1_REG, val);
+
+ escodec_unlock(sc);
+
+ return 0;
+}
+
+static int
+escodec_dai_add_device(audio_dai_tag_t dai, audio_dai_tag_t aux)
+{
+ struct escodec_softc * const sc = audio_dai_private(dai);
+
+ if (sc->sc_amplifier != NULL)
+ return 0;
+
+ sc->sc_amplifier = aux;
+
+ return 0;
+}
+
+static audio_dai_tag_t
+escodec_dai_get_tag(device_t dev, const void *data, size_t len)
+{
+ struct escodec_softc * const sc = device_private(dev);
+
+ if (len != 4)
+ return NULL;
+
+ return &sc->sc_dai;
+}
+
+static struct fdtbus_dai_controller_func escodec_dai_funcs = {
+ .get_tag = escodec_dai_get_tag
+};
+
+static void
+escodec_init(struct escodec_softc *sc)
+{
+ uint8_t val;
+
+ escodec_lock(sc);
+
+ escodec_write(sc, ESCODEC_RESET_REG, RESET_ALL);
+ delay(5000);
+ escodec_write(sc, ESCODEC_RESET_REG, RESET_CSM_ON);
+ delay(30000);
+
+ escodec_write(sc, ESCODEC_VMID_REG, 0xff);
+ escodec_write(sc, ESCODEC_ADC_OSR_REG, 0x32);
+
+ val = escodec_read(sc, ESCODEC_SD_ADC_REG);
+ val &= ~SD_FMT_WL;
+ val |= __SHIFTIN(SD_FMT_WL_16, SD_FMT_WL);
+ escodec_write(sc, ESCODEC_SD_ADC_REG, val);
+
+ val = escodec_read(sc, ESCODEC_SD_DAC_REG);
+ val &= ~SD_FMT_WL;
+ val |= __SHIFTIN(SD_FMT_WL_16, SD_FMT_WL);
+ escodec_write(sc, ESCODEC_SD_DAC_REG, val);
+
+ /* Power up */
+ escodec_write(sc, ESCODEC_PDN_REG, 0);
+
+ /* Route DAC signal to HP mixer */
+ val = HPMIXRT_LD2LHPMIX | HPMIXRT_RD2RHPMIX;
+ escodec_write(sc, ESCODEC_HPMIXRT_REG, val);
+
+ /* Power up DAC */
+ escodec_write(sc, ESCODEC_DACPWR_REG, 0);
+
+ /* Power up HP mixer and unmute */
+ escodec_write(sc, ESCODEC_HPMIX_REG, 0);
+
+ /* Power up HP output driver */
+ val = escodec_read(sc, ESCODEC_HPPWR_REG);
+ val &= ~HPPWR_PDN_CPHP;
+ escodec_write(sc, ESCODEC_HPPWR_REG, val);
+
+ /* Power up HP charge pump circuits */
+ val = escodec_read(sc, ESCODEC_CPPWR_REG);
+ val &= ~CPPWR_PDN_CP;
+ escodec_write(sc, ESCODEC_CPPWR_REG, val);
+
+ /* Set LIN1/RIN1 as inputs for HP mixer */
+ escodec_write(sc, ESCODEC_HPSEL_REG, 0);
+
+ /* Power up HP output driver calibration */
+ val = escodec_read(sc, ESCODEC_HPVOL_REG);
+ val &= ~HPVOL_PDN_LICAL;
+ val &= ~HPVOL_PDN_RICAL;
+ escodec_write(sc, ESCODEC_HPVOL_REG, val);
+
+ /* Set headphone mixer to -6dB */
+ escodec_write(sc, ESCODEC_HPMIXVOL_REG, 0x44);
+
+ /* Set charge pump headphone to -48dB */
+ escodec_write(sc, ESCODEC_HPVOL_REG, 0x33);
+
+ /* Set DAC to 0dB */
+ escodec_write(sc, ESCODEC_DACVOL_L_REG, 0);
+ escodec_write(sc, ESCODEC_DACVOL_R_REG, 0);
+
+ /* Enable HP output */
+ val = HPOUTEN_EN_HPL | HPOUTEN_EN_HPR |
+ HPOUTEN_HPL_OUTEN | HPOUTEN_HPR_OUTEN;
+ escodec_write(sc, ESCODEC_HPOUTEN_REG, val);
+
+ escodec_unlock(sc);
+}
+
+static int
+escodec_match(device_t parent, cfdata_t match, void *aux)
+{
+ struct i2c_attach_args *ia = aux;
+ int match_result;
+
+ if (iic_use_direct_match(ia, match, compat_data, &match_result))
+ return match_result;
+
+ /* This device is direct-config only */
+
+ return 0;
+}
+
+static void
+escodec_attach(device_t parent, device_t self, void *aux)
+{
+ struct escodec_softc * const sc = device_private(self);
+ struct i2c_attach_args * const ia = aux;
+ const int phandle = ia->ia_cookie;
+
+ sc->sc_dev = self;
+ sc->sc_i2c = ia->ia_tag;
+ sc->sc_addr = ia->ia_addr;
+ sc->sc_phandle = ia->ia_cookie;
+ sc->sc_swvol = 0xff;
+
+ sc->sc_clk = fdtbus_clock_get(phandle, "mclk");
+ if (sc->sc_clk == NULL || clk_enable(sc->sc_clk) != 0) {
+ aprint_error(": couldn't enable mclk\n");
+ return;
+ }
+
+ aprint_naive("\n");
+ aprint_normal(": Everest Semi ES8316 Audio CODEC\n");
+
+ escodec_init(sc);
+
+ sc->sc_dai.dai_set_sysclk = escodec_dai_set_sysclk;
+ sc->sc_dai.dai_set_format = escodec_dai_set_format;
+ sc->sc_dai.dai_add_device = escodec_dai_add_device;
+ sc->sc_dai.dai_hw_if = &escodec_hw_if;
+ sc->sc_dai.dai_dev = self;
+ sc->sc_dai.dai_priv = sc;
+ fdtbus_register_dai_controller(self, phandle, &escodec_dai_funcs);
+}
+
+CFATTACH_DECL_NEW(es8316ac, sizeof(struct escodec_softc),
+ escodec_match, escodec_attach, NULL, NULL);
diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c
index bd7bee5a6d4..1400bfcd35f 100644
--- a/sys/dev/i2c/files.i2c
+++ b/sys/dev/i2c/files.i2c
@@ -1,4 +1,4 @@
-# $NetBSD: files.i2c,v 1.100.2.1 2019/11/27 13:46:45 martin Exp $
+# $NetBSD: files.i2c,v 1.100.2.2 2020/01/21 10:39:58 martin Exp $
obsolete defflag opt_i2cbus.h I2C_SCAN
define i2cbus { }
@@ -378,3 +378,13 @@ file dev/i2c/twl4030.c twl
device tdahdmi: edid, videomode, drmkms, drmkms_i2c
attach tdahdmi at iic
file dev/i2c/tda19988.c tdahdmi
+
+# Everest Semi ES8316 Low Power Audio CODEC
+device es8316ac
+attach es8316ac at iic
+file dev/i2c/es8316ac.c es8316ac
+
+# CellWise CW2015 Fuel Gauge IC
+device cwfg: sysmon_envsys
+attach cwfg at iic
+file dev/i2c/cwfg.c cwfg
diff --git a/sys/dev/i2c/rkpmic.c b/sys/dev/i2c/rkpmic.c
index 9bea4458f07..0434b163337 100644
--- a/sys/dev/i2c/rkpmic.c
+++ b/sys/dev/i2c/rkpmic.c
@@ -1,4 +1,4 @@
-/* $NetBSD: rkpmic.c,v 1.3 2019/07/03 10:21:41 jmcneill Exp $ */
+/* $NetBSD: rkpmic.c,v 1.3.2.1 2020/01/21 10:39:58 martin Exp $ */
/*-
* Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca>
@@ -27,7 +27,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: rkpmic.c,v 1.3 2019/07/03 10:21:41 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: rkpmic.c,v 1.3.2.1 2020/01/21 10:39:58 martin Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@@ -37,13 +37,44 @@ __KERNEL_RCSID(0, "$NetBSD: rkpmic.c,v 1.3 2019/07/03 10:21:41 jmcneill Exp $");
#include <sys/bus.h>
#include <sys/kmem.h>
+#include <dev/clock_subr.h>
+
#include <dev/i2c/i2cvar.h>
+#include <dev/clk/clk_backend.h>
+
#include <dev/fdt/fdtvar.h>
+#define SECONDS_REG 0x00
+#define MINUTES_REG 0x01
+#define HOURS_REG 0x02
+#define DAYS_REG 0x03
+#define MONTHS_REG 0x04
+#define YEARS_REG 0x05
+#define WEEKS_REG 0x06
+
+#define RTC_CTRL_REG 0x10
+#define RTC_CTRL_READSEL __BIT(7)
+#define RTC_CTRL_GET_TIME __BIT(6)
+#define RTC_CTRL_SET_32_COUNTER __BIT(5)
+#define RTC_CTRL_TEST_MODE __BIT(4)
+#define RTC_CTRL_AMPM_MODE __BIT(3)
+#define RTC_CTRL_AUTO_COMP __BIT(2)
+#define RTC_CTRL_ROUND_30S __BIT(1)
+#define RTC_CTRL_STOP_RTC __BIT(0)
+
+#define RTC_INT_REG 0x12
+#define RTC_COMP_LSB_REG 0x13
+#define RTC_COMP_MSB_REG 0x14
#define CHIP_NAME_REG 0x17
#define CHIP_VER_REG 0x18
+#define CLK32OUT_REG 0x20
+#define CLK32OUT_CLKOUT2_EN __BIT(0)
+
+#define DEVCTRL_REG 0x4b
+#define DEVCTRL_DEV_OFF_RST __BIT(3)
+
struct rkpmic_ctrl {
const char * name;
uint8_t enable_reg;
@@ -60,6 +91,9 @@ struct rkpmic_config {
const char * name;
const struct rkpmic_ctrl *ctrl;
u_int nctrl;
+
+ u_int poweroff_reg;
+ u_int poweroff_mask;
};
static const struct rkpmic_ctrl rk805_ctrls[] = {
@@ -162,6 +196,14 @@ static const struct rkpmic_config rk808_config = {
.name = "RK808",
.ctrl = rk808_ctrls,
.nctrl = __arraycount(rk808_ctrls),
+ .poweroff_reg = DEVCTRL_REG,
+ .poweroff_mask = DEVCTRL_DEV_OFF_RST,
+};
+
+struct rkpmic_softc;
+
+struct rkpmic_clk {
+ struct clk base;
};
struct rkpmic_softc {
@@ -169,8 +211,10 @@ struct rkpmic_softc {
i2c_tag_t sc_i2c;
i2c_addr_t sc_addr;
int sc_phandle;
-
+ struct todr_chip_handle sc_todr;
struct rkpmic_config *sc_conf;
+ struct clk_domain sc_clkdom;
+ struct rkpmic_clk sc_clk[2];
};
struct rkreg_softc {
@@ -198,7 +242,7 @@ rkpmic_read(struct rkpmic_softc *sc, uint8_t reg, int flags)
error = iic_smbus_read_byte(sc->sc_i2c, sc->sc_addr, reg, &val, flags);
if (error != 0)
- aprint_error_dev(sc->sc_dev, "error reading reg %#x: %d\n", reg, error);
+ device_printf(sc->sc_dev, "error reading reg %#x: %d\n", reg, error);
return val;
}
@@ -210,7 +254,7 @@ rkpmic_write(struct rkpmic_softc *sc, uint8_t reg, uint8_t val, int flags)
error = iic_smbus_write_byte(sc->sc_i2c, sc->sc_addr, reg, val, flags);
if (error != 0)
- aprint_error_dev(sc->sc_dev, "error writing reg %#x: %d\n", reg, error);
+ device_printf(sc->sc_dev, "error writing reg %#x: %d\n", reg, error);
}
#define I2C_READ(sc, reg) rkpmic_read((sc), (reg), I2C_F_POLL)
@@ -219,6 +263,182 @@ rkpmic_write(struct rkpmic_softc *sc, uint8_t reg, uint8_t val, int flags)
#define I2C_UNLOCK(sc) iic_release_bus((sc)->sc_i2c, I2C_F_POLL)
static int
+rkpmic_todr_settime(todr_chip_handle_t ch, struct clock_ymdhms *dt)
+{
+ struct rkpmic_softc * const sc = ch->cookie;
+ uint8_t val;
+
+ if (dt->dt_year < 2000 || dt->dt_year >= 2100) {
+ device_printf(sc->sc_dev, "year out of range\n");
+ return EINVAL;
+ }
+
+ if (I2C_LOCK(sc))
+ return EBUSY;
+
+ val = I2C_READ(sc, RTC_CTRL_REG);
+ I2C_WRITE(sc, RTC_CTRL_REG, val | RTC_CTRL_STOP_RTC);
+ I2C_WRITE(sc, SECONDS_REG, bintobcd(dt->dt_sec));
+ I2C_WRITE(sc, MINUTES_REG, bintobcd(dt->dt_min));
+ I2C_WRITE(sc, HOURS_REG, bintobcd(dt->dt_hour));
+ I2C_WRITE(sc, DAYS_REG, bintobcd(dt->dt_day));
+ I2C_WRITE(sc, MONTHS_REG, bintobcd(dt->dt_mon));
+ I2C_WRITE(sc, YEARS_REG, bintobcd(dt->dt_year % 100));
+ I2C_WRITE(sc, WEEKS_REG, bintobcd(dt->dt_wday == 0 ? 7 : dt->dt_wday));
+ I2C_WRITE(sc, RTC_CTRL_REG, val);
+ I2C_UNLOCK(sc);
+
+ return 0;
+}
+
+static int
+rkpmic_todr_gettime(todr_chip_handle_t ch, struct clock_ymdhms *dt)
+{
+ struct rkpmic_softc * const sc = ch->cookie;
+ uint8_t val;
+
+ if (I2C_LOCK(sc))
+ return EBUSY;
+
+ val = I2C_READ(sc, RTC_CTRL_REG);
+ I2C_WRITE(sc, RTC_CTRL_REG, val | RTC_CTRL_GET_TIME | RTC_CTRL_READSEL);
+ delay(1000000 / 32768); /* wait one cycle for shadow regs to latch */
+ I2C_WRITE(sc, RTC_CTRL_REG, val | RTC_CTRL_READSEL);
+ dt->dt_sec = bcdtobin(I2C_READ(sc, SECONDS_REG));
+ dt->dt_min = bcdtobin(I2C_READ(sc, MINUTES_REG));
+ dt->dt_hour = bcdtobin(I2C_READ(sc, HOURS_REG));
+ dt->dt_day = bcdtobin(I2C_READ(sc, DAYS_REG));
+ dt->dt_mon = bcdtobin(I2C_READ(sc, MONTHS_REG));
+ dt->dt_year = 2000 + bcdtobin(I2C_READ(sc, YEARS_REG));
+ dt->dt_wday = bcdtobin(I2C_READ(sc, WEEKS_REG));
+ if (dt->dt_wday == 7)
+ dt->dt_wday = 0;
+ I2C_WRITE(sc, RTC_CTRL_REG, val);
+ I2C_UNLOCK(sc);
+
+ /*
+ * RK808 has a hw bug which makes the 31st of November a valid day.
+ * If we detect the 31st of November we skip ahead one day.
+ * If the system has been turned off during the crossover the clock
+ * will have lost a day. No easy way to detect this. Oh well.
+ */
+ if (dt->dt_mon == 11 && dt->dt_day == 31) {
+ dt->dt_day--;
+ clock_secs_to_ymdhms(clock_ymdhms_to_secs(dt) + 86400, dt);
+ rkpmic_todr_settime(ch, dt);
+ }
+
+#if 0
+ device_printf(sc->sc_dev, "%04" PRIu64 "-%02u-%02u (%u) %02u:%02u:%02u\n",
+ dt->dt_year, dt->dt_mon, dt->dt_day, dt->dt_wday,
+ dt->dt_hour, dt->dt_min, dt->dt_sec);
+#endif
+
+ return 0;
+}
+
+static struct clk *
+rkpmic_clk_decode(device_t dev, int cc_phandle, const void *data, size_t len)
+{
+ struct rkpmic_softc * const sc = device_private(dev);
+
+ if (len != 4)
+ return NULL;
+
+ const u_int id = be32dec(data);
+ if (id >= __arraycount(sc->sc_clk))
+ return NULL;
+
+ return &sc->sc_clk[id].base;
+}
+
+static const struct fdtbus_clock_controller_func rkpmic_clk_fdt_funcs = {
+ .decode = rkpmic_clk_decode
+};
+
+static struct clk *
+rkpmic_clk_get(void *priv, const char *name)
+{
+ struct rkpmic_softc * const sc = priv;
+ u_int n;
+
+ for (n = 0; n < __arraycount(sc->sc_clk); n++) {
+ if (strcmp(name, sc->sc_clk[n].base.name) == 0)
+ return &sc->sc_clk[n].base;
+ }
+
+ return NULL;
+}
+
+static u_int
+rkpmic_clk_get_rate(void *priv, struct clk *clk)
+{
+ return 32768;
+}
+
+static int
+rkpmic_clk_enable(void *priv, struct clk *clk)
+{
+ struct rkpmic_softc * const sc = priv;
+ uint8_t val;
+
+ if (clk != &sc->sc_clk[1].base)
+ return 0;
+
+ I2C_LOCK(sc);
+ val = I2C_READ(sc, CLK32OUT_REG);
+ val |= CLK32OUT_CLKOUT2_EN;
+ I2C_WRITE(sc, CLK32OUT_REG, val);
+ I2C_UNLOCK(sc);
+
+ return 0;
+}
+
+static int
+rkpmic_clk_disable(void *priv, struct clk *clk)
+{
+ struct rkpmic_softc * const sc = priv;
+ uint8_t val;
+
+ if (clk != &sc->sc_clk[1].base)
+ return EIO;
+
+ I2C_LOCK(sc);
+ val = I2C_READ(sc, CLK32OUT_REG);
+ val &= ~CLK32OUT_CLKOUT2_EN;
+ I2C_WRITE(sc, CLK32OUT_REG, val);
+ I2C_UNLOCK(sc);
+
+ return 0;
+}
+
+static const struct clk_funcs rkpmic_clk_funcs = {
+ .get = rkpmic_clk_get,
+ .get_rate = rkpmic_clk_get_rate,
+ .enable = rkpmic_clk_enable,
+ .disable = rkpmic_clk_disable,
+};
+
+static void
+rkpmic_power_poweroff(device_t dev)
+{
+ struct rkpmic_softc * const sc = device_private(dev);
+ uint8_t val;
+
+ delay(1000000);
+
+ I2C_LOCK(sc);
+ val = I2C_READ(sc, sc->sc_conf->poweroff_reg);
+ val |= sc->sc_conf->poweroff_mask;
+ I2C_WRITE(sc, sc->sc_conf->poweroff_reg, val);
+ I2C_UNLOCK(sc);
+}
+
+static struct fdtbus_power_controller_func rkpmic_power_funcs = {
+ .poweroff = rkpmic_power_poweroff,
+};
+
+static int
rkpmic_match(device_t parent, cfdata_t match, void *aux)
{
struct i2c_attach_args *ia = aux;
@@ -248,15 +468,46 @@ rkpmic_attach(device_t parent, device_t self, void *aux)
sc->sc_phandle = ia->ia_cookie;
sc->sc_conf = (void *)entry->data;
+ memset(&sc->sc_todr, 0, sizeof(sc->sc_todr));
+ sc->sc_todr.cookie = sc;
+ sc->sc_todr.todr_gettime_ymdhms = rkpmic_todr_gettime;
+ sc->sc_todr.todr_settime_ymdhms = rkpmic_todr_settime;
+
aprint_naive("\n");
- aprint_normal(": %s Power Management IC\n", sc->sc_conf->name);
+ aprint_normal(": %s Power Management and Real Time Clock IC\n", sc->sc_conf->name);
I2C_LOCK(sc);
chipid = I2C_READ(sc, CHIP_NAME_REG) << 8;
chipid |= I2C_READ(sc, CHIP_VER_REG);
aprint_debug_dev(self, "Chip ID 0x%04x\n", chipid);
+ I2C_WRITE(sc, RTC_CTRL_REG, 0x0);
+ I2C_WRITE(sc, RTC_INT_REG, 0);
+ I2C_WRITE(sc, RTC_COMP_LSB_REG, 0);
+ I2C_WRITE(sc, RTC_COMP_MSB_REG, 0);
I2C_UNLOCK(sc);
+ fdtbus_todr_attach(self, sc->sc_phandle, &sc->sc_todr);
+
+ sc->sc_clkdom.name = device_xname(self);
+ sc->sc_clkdom.funcs = &rkpmic_clk_funcs;
+ sc->sc_clkdom.priv = sc;
+
+ sc->sc_clk[0].base.domain = &sc->sc_clkdom;
+ sc->sc_clk[0].base.name = "xin32k";
+ clk_attach(&sc->sc_clk[0].base);
+
+ sc->sc_clk[1].base.domain = &sc->sc_clkdom;
+ sc->sc_clk[1].base.name = "clkout2";
+ clk_attach(&sc->sc_clk[1].base);
+
+ fdtbus_register_clock_controller(self, sc->sc_phandle,
+ &rkpmic_clk_fdt_funcs);
+
+ if (of_hasprop(sc->sc_phandle, "rockchip,system-power-controller") &&
+ sc->sc_conf->poweroff_mask != 0)
+ fdtbus_register_power_controller(self, sc->sc_phandle,
+ &rkpmic_power_funcs);
+
regulators = of_find_firstchild_byname(sc->sc_phandle, "regulators");
if (regulators < 0)
return;
diff --git a/sys/dev/ic/anx_dp.c b/sys/dev/ic/anx_dp.c
new file mode 100644
index 00000000000..ba731ce66c5
--- /dev/null
+++ b/sys/dev/ic/anx_dp.c
@@ -0,0 +1,1079 @@
+/* $NetBSD: anx_dp.c,v 1.2.4.2 2020/01/21 10:39:58 martin Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jonathan A. Kollasch <jakllsch@kollasch.net>
+ * 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 ``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 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: anx_dp.c,v 1.2.4.2 2020/01/21 10:39:58 martin Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+
+#include <dev/ic/anx_dp.h>
+
+#if ANXDP_AUDIO
+#include <dev/audio/audio_dai.h>
+#endif
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_edid.h>
+
+#define ANXDP_DP_TX_VERSION 0x010
+#define ANXDP_TX_SW_RESET 0x014
+#define RESET_DP_TX __BIT(0)
+#define ANXDP_FUNC_EN_1 0x018
+#define MASTER_VID_FUNC_EN_N __BIT(7)
+#define RK_VID_CAP_FUNC_EN_N __BIT(6)
+#define SLAVE_VID_FUNC_EN_N __BIT(5)
+#define RK_VID_FIFO_FUNC_EN_N __BIT(5)
+#define AUD_FIFO_FUNC_EN_N __BIT(4)
+#define AUD_FUNC_EN_N __BIT(3)
+#define HDCP_FUNC_EN_N __BIT(2)
+#define CRC_FUNC_EN_N __BIT(1)
+#define SW_FUNC_EN_N __BIT(0)
+#define ANXDP_FUNC_EN_2 0x01c
+#define SSC_FUNC_EN_N __BIT(7)
+#define AUX_FUNC_EN_N __BIT(2)
+#define SERDES_FIFO_FUNC_EN_N __BIT(1)
+#define LS_CLK_DOMAIN_FUNC_EN_N __BIT(0)
+#define ANXDP_VIDEO_CTL_1 0x020
+#define VIDEO_EN __BIT(7)
+#define VIDEO_MUTE __BIT(6)
+#define ANXDP_VIDEO_CTL_2 0x024
+#define ANXDP_VIDEO_CTL_3 0x028
+#define ANXDP_VIDEO_CTL_4 0x02c
+#define ANXDP_VIDEO_CTL_8 0x03c
+#define ANXDP_VIDEO_CTL_10 0x044
+#define F_SEL __BIT(4)
+#define SLAVE_I_SCAN_CFG __BIT(2)
+#define SLAVE_VSYNC_P_CFG __BIT(1)
+#define SLAVE_HSYNC_P_CFG __BIT(0)
+#define ANXDP_PLL_REG_1 0x0fc
+#define REF_CLK_24M __BIT(0)
+#define RKANXDP_PD 0x12c
+#define DP_INC_BG __BIT(7)
+#define DP_EXP_PD __BIT(6)
+#define DP_PHY_PD __BIT(5)
+#define RK_AUX_PD __BIT(5)
+#define AUX_PD __BIT(4)
+#define RK_PLL_PD __BIT(4)
+#define CHx_PD(x) __BIT(x) /* 0<=x<=3 */
+#define DP_ALL_PD __BITS(7,0)
+#define ANXDP_LANE_MAP 0x35c
+#define ANXDP_ANALOG_CTL_1 0x370
+#define TX_TERMINAL_CTRL_50_OHM __BIT(4)
+#define ANXDP_ANALOG_CTL_2 0x374
+#define SEL_24M __BIT(3)
+#define TX_DVDD_BIT_1_0625V 0x4
+#define ANXDP_ANALOG_CTL_3 0x378
+#define DRIVE_DVDD_BIT_1_0625V (0x4 << 5)
+#define VCO_BIT_600_MICRO (0x5 << 0)
+#define ANXDP_PLL_FILTER_CTL_1 0x37c
+#define PD_RING_OSC __BIT(6)
+#define AUX_TERMINAL_CTRL_50_OHM (2 << 4)
+#define TX_CUR1_2X __BIT(2)
+#define TX_CUR_16_MA 3
+#define ANXDP_TX_AMP_TUNING_CTL 0x380
+#define ANXDP_AUX_HW_RETRY_CTL 0x390
+#define AUX_BIT_PERIOD_EXPECTED_DELAY(x) __SHIFTIN((x), __BITS(10,8))
+#define AUX_HW_RETRY_INTERVAL_600_US __SHIFTIN(0, __BITS(4,3))
+#define AUX_HW_RETRY_INTERVAL_800_US __SHIFTIN(1, __BITS(4,3))
+#define AUX_HW_RETRY_INTERVAL_1000_US __SHIFTIN(2, __BITS(4,3))
+#define AUX_HW_RETRY_INTERVAL_1800_US __SHIFTIN(3, __BITS(4,3))
+#define AUX_HW_RETRY_COUNT_SEL(x) __SHIFTIN((x), __BITS(2,0))
+#define ANXDP_COMMON_INT_STA_1 0x3c4
+#define PLL_LOCK_CHG __BIT(6)
+#define ANXDP_COMMON_INT_STA_2 0x3c8
+#define ANXDP_COMMON_INT_STA_3 0x3cc
+#define ANXDP_COMMON_INT_STA_4 0x3d0
+#define ANXDP_DP_INT_STA 0x3dc
+#define INT_HPD __BIT(6)
+#define HW_TRAINING_FINISH __BIT(5)
+#define RPLY_RECEIV __BIT(1)
+#define AUX_ERR __BIT(0)
+#define ANXDP_SYS_CTL_1 0x600
+#define DET_STA __BIT(2)
+#define FORCE_DET __BIT(1)
+#define DET_CTRL __BIT(0)
+#define ANXDP_SYS_CTL_2 0x604
+#define ANXDP_SYS_CTL_3 0x608
+#define HPD_STATUS __BIT(6)
+#define F_HPD __BIT(5)
+#define HPD_CTRL __BIT(4)
+#define HDCP_RDY __BIT(3)
+#define STRM_VALID __BIT(2)
+#define F_VALID __BIT(1)
+#define VALID_CTRL __BIT(0)
+#define ANXDP_SYS_CTL_4 0x60c
+#define ANXDP_PKT_SEND_CTL 0x640
+#define ANXDP_HDCP_CTL 0x648
+#define ANXDP_LINK_BW_SET 0x680
+#define ANXDP_LANE_COUNT_SET 0x684
+#define ANXDP_TRAINING_PTN_SET 0x688
+#define SCRAMBLING_DISABLE __BIT(5)
+#define SW_TRAINING_PATTERN_SET_PTN2 __SHIFTIN(2, __BITS(1,0))
+#define SW_TRAINING_PATTERN_SET_PTN1 __SHIFTIN(1, __BITS(1,0))
+#define ANXDP_LNx_LINK_TRAINING_CTL(x) (0x68c + 4 * (x)) /* 0 <= x <= 3 */
+#define MAX_PRE_REACH __BIT(5)
+#define PRE_EMPHASIS_SET(x) __SHIFTIN((x), __BITS(4,3))
+#define MAX_DRIVE_REACH __BIT(2)
+#define DRIVE_CURRENT_SET(x) __SHIFTIN((x), __BITS(1,0))
+#define ANXDP_DEBUG_CTL 0x6c0
+#define PLL_LOCK __BIT(4)
+#define F_PLL_LOCK __BIT(3)
+#define PLL_LOCK_CTRL __BIT(2)
+#define PN_INV __BIT(0)
+#define ANXDP_LINK_DEBUG_CTL 0x6e0
+#define ANXDP_PLL_CTL 0x71c
+#define ANXDP_PHY_PD 0x720
+#define ANXDP_PHY_TEST 0x724
+#define MACRO_RST __BIT(5)
+#define ANXDP_M_AUD_GEN_FILTER_TH 0x778
+#define ANXDP_AUX_CH_STA 0x780
+#define AUX_BUSY __BIT(4)
+#define AUX_STATUS(x) __SHIFTOUT((x), __BITS(3,0))
+#define ANXDP_AUX_ERR_NUM 0x784
+#define ANXDP_AUX_CH_DEFER_CTL 0x788
+#define DEFER_CTRL_EN __BIT(7)
+#define DEFER_COUNT(x) __SHIFTIN((x), __BITS(6,0))
+#define ANXDP_AUX_RX_COMM 0x78c
+#define AUX_RX_COMM_I2C_DEFER __BIT(3)
+#define AUX_RX_COMM_AUX_DEFER __BIT(1)
+#define ANXDP_BUFFER_DATA_CTL 0x790
+#define BUF_CLR __BIT(7)
+#define BUF_DATA_COUNT(x) __SHIFTIN((x), __BITS(4,0))
+#define ANXDP_AUX_CH_CTL_1 0x794
+#define AUX_LENGTH(x) __SHIFTIN((x) - 1, __BITS(7,4))
+#define AUX_TX_COMM(x) __SHIFTOUT(x, __BITS(3,0))
+#define AUX_TX_COMM_DP __BIT(3)
+#define AUX_TX_COMM_MOT __BIT(2)
+#define AUX_TX_COMM_READ __BIT(0)
+#define ANXDP_AUX_ADDR_7_0 0x798
+#define AUX_ADDR_7_0(x) (((x) >> 0) & 0xff)
+#define ANXDP_AUX_ADDR_15_8 0x79c
+#define AUX_ADDR_15_8(x) (((x) >> 8) & 0xff)
+#define ANXDP_AUX_ADDR_19_16 0x7a0
+#define AUX_ADDR_19_16(x) (((x) >> 16) & 0xf)
+#define ANXDP_AUX_CH_CTL_2 0x7a4
+#define ADDR_ONLY __BIT(1)
+#define AUX_EN __BIT(0)
+#define ANXDP_BUF_DATA(x) (0x7c0 + 4 * (x))
+#define ANXDP_SOC_GENERAL_CTL 0x800
+#define AUDIO_MODE_SPDIF_MODE __BIT(8)
+#define VIDEO_MODE_SLAVE_MODE __BIT(1)
+#define ANXDP_CRC_CON 0x890
+#define ANXDP_PLL_REG_2 0x9e4
+#define ANXDP_PLL_REG_3 0x9e8
+#define ANXDP_PLL_REG_4 0x9ec
+#define ANXDP_PLL_REG_5 0xa00
+
+#if ANXDP_AUDIO
+enum anxdp_dai_mixer_ctrl {
+ ANXDP_DAI_OUTPUT_CLASS,
+ ANXDP_DAI_INPUT_CLASS,
+
+ ANXDP_DAI_OUTPUT_MASTER_VOLUME,
+ ANXDP_DAI_INPUT_DAC_VOLUME,
+
+ ANXDP_DAI_MIXER_CTRL_LAST
+};
+
+static void
+anxdp_audio_init(struct anxdp_softc *sc)
+{
+}
+#endif
+
+static inline const bool
+isrockchip(struct anxdp_softc * const sc)
+{
+ return (sc->sc_flags & ANXDP_FLAG_ROCKCHIP) != 0;
+}
+
+static enum drm_connector_status
+anxdp_connector_detect(struct drm_connector *connector, bool force)
+{
+#if 0
+ struct anxdp_connector *anxdp_connector = to_anxdp_connector(connector);
+ struct anxdp_softc * const sc = anxdp_connector->sc;
+
+ /* XXX HPD */
+#endif
+ return connector_status_connected;
+}
+
+static void
+anxdp_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs anxdp_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = anxdp_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = anxdp_connector_destroy,
+};
+
+static void
+anxdp_analog_power_up_all(struct anxdp_softc * const sc)
+{
+ const bus_size_t pd_reg = isrockchip(sc) ? RKANXDP_PD : ANXDP_PHY_PD;
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, pd_reg, DP_ALL_PD);
+ delay(15);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, pd_reg,
+ DP_ALL_PD & ~DP_INC_BG);
+ delay(15);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, pd_reg, 0);
+}
+
+static int
+anxdp_await_pll_lock(struct anxdp_softc * const sc)
+{
+ u_int timeout;
+
+ for (timeout = 0; timeout < 100; timeout++) {
+ if ((bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_DEBUG_CTL) &
+ PLL_LOCK) != 0)
+ return 0;
+ delay(20);
+ }
+
+ return ETIMEDOUT;
+}
+
+static void
+anxdp_init_hpd(struct anxdp_softc * const sc)
+{
+ uint32_t sc3;
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_COMMON_INT_STA_4, 0x7);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_DP_INT_STA, INT_HPD);
+
+ sc3 = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_3);
+ sc3 &= ~(F_HPD | HPD_CTRL);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_3, sc3);
+
+ sc3 = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_3);
+ sc3 |= F_HPD | HPD_CTRL;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_3, sc3);
+}
+
+static void
+anxdp_init_aux(struct anxdp_softc * const sc)
+{
+ uint32_t fe2, pd, hrc;
+ const bus_size_t pd_reg = isrockchip(sc) ? RKANXDP_PD : ANXDP_PHY_PD;
+ const uint32_t pd_mask = isrockchip(sc) ? RK_AUX_PD : AUX_PD;
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_DP_INT_STA,
+ RPLY_RECEIV | AUX_ERR);
+
+ pd = bus_space_read_4(sc->sc_bst, sc->sc_bsh, pd_reg);
+ pd |= pd_mask;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, pd_reg, pd);
+
+ delay(11);
+
+ pd = bus_space_read_4(sc->sc_bst, sc->sc_bsh, pd_reg);
+ pd &= ~pd_mask;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, pd_reg, pd);
+
+ fe2 = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_2);
+ fe2 |= AUX_FUNC_EN_N;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_2, fe2);
+
+ hrc = AUX_HW_RETRY_COUNT_SEL(0) | AUX_HW_RETRY_INTERVAL_600_US;
+ if (!isrockchip(sc))
+ hrc |= AUX_BIT_PERIOD_EXPECTED_DELAY(3);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_HW_RETRY_CTL, hrc);
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_CH_DEFER_CTL,
+ DEFER_CTRL_EN | DEFER_COUNT(1));
+
+ fe2 = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_2);
+ fe2 &= ~AUX_FUNC_EN_N;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_2, fe2);
+}
+
+static int
+anxdp_connector_get_modes(struct drm_connector *connector)
+{
+ struct anxdp_connector *anxdp_connector = to_anxdp_connector(connector);
+ struct anxdp_softc * const sc = anxdp_connector->sc;
+ struct edid *pedid = NULL;
+ int error;
+
+ pedid = drm_get_edid(connector, &sc->sc_dpaux.ddc);
+
+#if ANXDP_AUDIO
+ if (pedid) {
+ anxdp_connector->monitor_audio =
+ drm_detect_monitor_audio(pedid);
+ } else {
+ anxdp_connector->monitor_audio = false;
+ }
+
+#endif
+ drm_mode_connector_update_edid_property(connector, pedid);
+ if (pedid == NULL)
+ return 0;
+
+ error = drm_add_edid_modes(connector, pedid);
+ drm_edid_to_eld(connector, pedid);
+
+ if (pedid != NULL)
+ kfree(pedid);
+
+ return error;
+}
+
+static struct drm_encoder *
+anxdp_connector_best_encoder(struct drm_connector *connector)
+{
+ int enc_id = connector->encoder_ids[0];
+ struct drm_mode_object *obj;
+ struct drm_encoder *encoder = NULL;
+
+ if (enc_id) {
+ obj = drm_mode_object_find(connector->dev, enc_id,
+ DRM_MODE_OBJECT_ENCODER);
+ if (obj == NULL)
+ return NULL;
+ encoder = obj_to_encoder(obj);
+ }
+
+ return encoder;
+}
+
+static const struct drm_connector_helper_funcs anxdp_connector_helper_funcs = {
+ .get_modes = anxdp_connector_get_modes,
+ .best_encoder = anxdp_connector_best_encoder,
+};
+
+static int
+anxdp_bridge_attach(struct drm_bridge *bridge)
+{
+ struct anxdp_softc * const sc = bridge->driver_private;
+ struct anxdp_connector *anxdp_connector = &sc->sc_connector;
+ struct drm_connector *connector = &anxdp_connector->base;
+ int error;
+
+ anxdp_connector->sc = sc;
+
+ connector->polled =
+ DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
+ connector->interlace_allowed = 0;
+ connector->doublescan_allowed = 0;
+
+ drm_connector_init(bridge->dev, connector, &anxdp_connector_funcs,
+ connector->connector_type);
+ drm_connector_helper_add(connector, &anxdp_connector_helper_funcs);
+
+ error = drm_mode_connector_attach_encoder(connector, bridge->encoder);
+ if (error != 0)
+ return error;
+
+ return drm_connector_register(connector);
+}
+
+static void
+anxdp_macro_reset(struct anxdp_softc * const sc)
+{
+ uint32_t val;
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_PHY_TEST);
+ val |= MACRO_RST;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_PHY_TEST, val);
+ delay(10);
+ val &= ~MACRO_RST;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_PHY_TEST, val);
+}
+
+static void
+anxdp_link_start(struct anxdp_softc * const sc, struct drm_dp_link * const link)
+{
+ uint8_t training[4];
+ uint32_t val;
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_LINK_BW_SET, drm_dp_link_rate_to_bw_code(link->rate));
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_LANE_COUNT_SET, link->num_lanes);
+ if (0 != drm_dp_link_configure(&sc->sc_dpaux, link))
+ return;
+
+ for (u_int i = 0; i < link->num_lanes; i++) {
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
+ ANXDP_LNx_LINK_TRAINING_CTL(i));
+ val &= ~(PRE_EMPHASIS_SET(3)|DRIVE_CURRENT_SET(3));
+ val |= PRE_EMPHASIS_SET(0);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh,
+ ANXDP_LNx_LINK_TRAINING_CTL(i), val);
+ }
+
+ if (anxdp_await_pll_lock(sc) != 0) {
+ device_printf(sc->sc_dev, "PLL lock timeout\n");
+ }
+
+ for (u_int i = 0; i < link->num_lanes; i++) {
+ training[i] = DP_TRAIN_PRE_EMPH_LEVEL_0 |
+ DP_TRAIN_VOLTAGE_SWING_LEVEL_0;
+ }
+
+ drm_dp_dpcd_write(&sc->sc_dpaux, DP_TRAINING_LANE0_SET, training,
+ link->num_lanes);
+}
+
+static void
+anxdp_process_clock_recovery(struct anxdp_softc * const sc,
+ struct drm_dp_link * const link)
+{
+ u_int i, tries;
+ uint8_t link_status[DP_LINK_STATUS_SIZE];
+ uint8_t training[4];
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_TRAINING_PTN_SET,
+ SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN1);
+ drm_dp_dpcd_writeb(&sc->sc_dpaux, DP_TRAINING_PATTERN_SET,
+ DP_LINK_SCRAMBLING_DISABLE | DP_TRAINING_PATTERN_1);
+
+ tries = 0;
+again:
+ if (tries++ >= 10) {
+ device_printf(sc->sc_dev, "cr fail\n");
+ return;
+ }
+ drm_dp_link_train_clock_recovery_delay(sc->sc_dpcd);
+ if (DP_LINK_STATUS_SIZE !=
+ drm_dp_dpcd_read_link_status(&sc->sc_dpaux, link_status)) {
+ return;
+ }
+ if (!drm_dp_clock_recovery_ok(link_status, link->num_lanes)) {
+ goto cr_fail;
+ }
+
+ return;
+
+cr_fail:
+ for (i = 0; i < link->num_lanes; i++) {
+ uint8_t vs, pe;
+ vs = drm_dp_get_adjust_request_voltage(link_status, i);
+ pe = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+ training[i] = vs | pe;
+ }
+ for (i = 0; i < link->num_lanes; i++) {
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh,
+ ANXDP_LNx_LINK_TRAINING_CTL(i), training[i]);
+ }
+ drm_dp_dpcd_write(&sc->sc_dpaux, DP_TRAINING_LANE0_SET, training,
+ link->num_lanes);
+ goto again;
+}
+
+static void
+anxdp_process_eq(struct anxdp_softc * const sc, struct drm_dp_link * const link)
+{
+ u_int i, tries;
+ uint8_t link_status[DP_LINK_STATUS_SIZE];
+ uint8_t training[4];
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_TRAINING_PTN_SET,
+ SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN2);
+ drm_dp_dpcd_writeb(&sc->sc_dpaux, DP_TRAINING_PATTERN_SET,
+ DP_LINK_SCRAMBLING_DISABLE | DP_TRAINING_PATTERN_2);
+
+ tries = 0;
+again:
+ if (tries++ >= 10) {
+ device_printf(sc->sc_dev, "eq fail\n");
+ return;
+ }
+ drm_dp_link_train_channel_eq_delay(sc->sc_dpcd);
+ if (DP_LINK_STATUS_SIZE !=
+ drm_dp_dpcd_read_link_status(&sc->sc_dpaux, link_status)) {
+ return;
+ }
+ if (!drm_dp_channel_eq_ok(link_status, link->num_lanes)) {
+ goto eq_fail;
+ }
+
+ return;
+
+eq_fail:
+ for (i = 0; i < link->num_lanes; i++) {
+ uint8_t vs, pe;
+ vs = drm_dp_get_adjust_request_voltage(link_status, i);
+ pe = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+ training[i] = vs | pe;
+ }
+ for (i = 0; i < link->num_lanes; i++) {
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh,
+ ANXDP_LNx_LINK_TRAINING_CTL(i), training[i]);
+ }
+ drm_dp_dpcd_write(&sc->sc_dpaux, DP_TRAINING_LANE0_SET, training,
+ link->num_lanes);
+ goto again;
+}
+
+static void
+anxdp_train_link(struct anxdp_softc * const sc)
+{
+ struct drm_dp_link link;
+
+ anxdp_macro_reset(sc);
+
+ if (0 != drm_dp_link_probe(&sc->sc_dpaux, &link)) {
+ device_printf(sc->sc_dev, "link probe failed\n");
+ return;
+ }
+ if (0 != drm_dp_link_power_up(&sc->sc_dpaux, &link))
+ return;
+ if (DP_RECEIVER_CAP_SIZE != drm_dp_dpcd_read(&sc->sc_dpaux, DP_DPCD_REV,
+ sc->sc_dpcd, DP_RECEIVER_CAP_SIZE))
+ return;
+
+ anxdp_link_start(sc, &link);
+ anxdp_process_clock_recovery(sc, &link);
+ anxdp_process_eq(sc, &link);
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_TRAINING_PTN_SET, 0);
+ drm_dp_dpcd_writeb(&sc->sc_dpaux, DP_TRAINING_PATTERN_SET,
+ DP_TRAINING_PATTERN_DISABLE);
+
+}
+
+static void
+anxdp_bringup(struct anxdp_softc * const sc)
+{
+ uint32_t val;
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_VIDEO_CTL_1);
+ val &= ~VIDEO_EN;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_VIDEO_CTL_1, val);
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_VIDEO_CTL_1);
+ val &= ~VIDEO_MUTE;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_VIDEO_CTL_1, val);
+
+ val = SW_FUNC_EN_N;
+ if (isrockchip(sc)) {
+ val |= RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N;
+ } else {
+ val |= MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N |
+ AUD_FIFO_FUNC_EN_N | AUD_FUNC_EN_N | HDCP_FUNC_EN_N;
+ }
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_1, val);
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_2,
+ SSC_FUNC_EN_N | AUX_FUNC_EN_N | SERDES_FIFO_FUNC_EN_N |
+ LS_CLK_DOMAIN_FUNC_EN_N);
+
+ delay(30);
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_M_AUD_GEN_FILTER_TH, 2);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_SOC_GENERAL_CTL, 0x101);
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_TX_SW_RESET,
+ RESET_DP_TX);
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_ANALOG_CTL_1,
+ TX_TERMINAL_CTRL_50_OHM);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_ANALOG_CTL_2,
+ SEL_24M | TX_DVDD_BIT_1_0625V);
+ if (isrockchip(sc)) {
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_PLL_REG_1,
+ REF_CLK_24M);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_PLL_REG_2,
+ 0x95);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_PLL_REG_3,
+ 0x40);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_PLL_REG_4,
+ 0x58);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_PLL_REG_5,
+ 0x22);
+ }
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_ANALOG_CTL_3,
+ DRIVE_DVDD_BIT_1_0625V | VCO_BIT_600_MICRO);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_PLL_FILTER_CTL_1,
+ PD_RING_OSC | AUX_TERMINAL_CTRL_50_OHM | TX_CUR1_2X | TX_CUR_16_MA);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_TX_AMP_TUNING_CTL, 0);
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_1);
+ val &= ~SW_FUNC_EN_N;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_1, val);
+
+ anxdp_analog_power_up_all(sc);
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_COMMON_INT_STA_1,
+ PLL_LOCK_CHG);
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_DEBUG_CTL);
+ val &= ~(F_PLL_LOCK | PLL_LOCK_CTRL);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_DEBUG_CTL, val);
+
+ if (anxdp_await_pll_lock(sc) != 0) {
+ device_printf(sc->sc_dev, "PLL lock timeout\n");
+ }
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_2);
+ val &= ~(SERDES_FIFO_FUNC_EN_N | LS_CLK_DOMAIN_FUNC_EN_N |
+ AUX_FUNC_EN_N);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_2, val);
+
+ anxdp_init_hpd(sc);
+ anxdp_init_aux(sc);
+}
+
+static void
+anxdp_bridge_enable(struct drm_bridge *bridge)
+{
+ struct anxdp_softc * const sc = bridge->driver_private;
+ uint32_t val;
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_1);
+ if (isrockchip(sc)) {
+ val &= ~(RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N);
+ } else {
+ val &= ~(MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N);
+ val |= MASTER_VID_FUNC_EN_N;
+ }
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_FUNC_EN_1, val);
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_VIDEO_CTL_10);
+ val &= ~(SLAVE_I_SCAN_CFG|SLAVE_VSYNC_P_CFG|SLAVE_HSYNC_P_CFG);
+ if ((sc->sc_curmode.flags & DRM_MODE_FLAG_INTERLACE) != 0)
+ val |= SLAVE_I_SCAN_CFG;
+ if ((sc->sc_curmode.flags & DRM_MODE_FLAG_NVSYNC) != 0)
+ val |= SLAVE_VSYNC_P_CFG;
+ if ((sc->sc_curmode.flags & DRM_MODE_FLAG_NHSYNC) != 0)
+ val |= SLAVE_HSYNC_P_CFG;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_VIDEO_CTL_10, val);
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_SOC_GENERAL_CTL,
+ AUDIO_MODE_SPDIF_MODE | VIDEO_MODE_SLAVE_MODE);
+
+ anxdp_train_link(sc);
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_VIDEO_CTL_1);
+ val |= VIDEO_EN;
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_VIDEO_CTL_1, val);
+
+ if (sc->sc_panel != NULL &&
+ sc->sc_panel->funcs != NULL &&
+ sc->sc_panel->funcs->enable != NULL)
+ sc->sc_panel->funcs->enable(sc->sc_panel);
+#if ANXDP_AUDIO
+
+ if (sc->sc_connector.monitor_audio)
+ anxdp_audio_init(sc);
+#endif
+}
+
+static void
+anxdp_bridge_pre_enable(struct drm_bridge *bridge)
+{
+}
+
+static void
+anxdp_bridge_disable(struct drm_bridge *bridge)
+{
+}
+
+static void
+anxdp_bridge_post_disable(struct drm_bridge *bridge)
+{
+}
+
+static void
+anxdp_bridge_mode_set(struct drm_bridge *bridge,
+ struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+ struct anxdp_softc * const sc = bridge->driver_private;
+
+ sc->sc_curmode = *adjusted_mode;
+}
+
+static bool
+anxdp_bridge_mode_fixup(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static const struct drm_bridge_funcs anxdp_bridge_funcs = {
+ .attach = anxdp_bridge_attach,
+ .enable = anxdp_bridge_enable,
+ .pre_enable = anxdp_bridge_pre_enable,
+ .disable = anxdp_bridge_disable,
+ .post_disable = anxdp_bridge_post_disable,
+ .mode_set = anxdp_bridge_mode_set,
+ .mode_fixup = anxdp_bridge_mode_fixup,
+};
+
+#if ANXDP_AUDIO
+static int
+anxdp_dai_set_format(audio_dai_tag_t dai, u_int format)
+{
+ return 0;
+}
+
+static int
+anxdp_dai_add_device(audio_dai_tag_t dai, audio_dai_tag_t aux)
+{
+ /* Not supported */
+ return 0;
+}
+
+static void
+anxdp_audio_swvol_codec(audio_filter_arg_t *arg)
+{
+ struct anxdp_softc * const sc = arg->context;
+ const aint_t *src;
+ aint_t *dst;
+ u_int sample_count;
+ u_int i;
+
+ src = arg->src;
+ dst = arg->dst;
+ sample_count = arg->count * arg->srcfmt->channels;
+ for (i = 0; i < sample_count; i++) {
+ aint2_t v = (aint2_t)(*src++);
+ v = v * sc->sc_swvol / 255;
+ *dst++ = (aint_t)v;
+ }
+}
+
+static int
+anxdp_audio_set_format(void *priv, int setmode,
+ const audio_params_t *play, const audio_params_t *rec,
+ audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
+{
+ struct anxdp_softc * const sc = priv;
+
+ pfil->codec = anxdp_audio_swvol_codec;
+ pfil->context = sc;
+
+ return 0;
+}
+
+static int
+anxdp_audio_set_port(void *priv, mixer_ctrl_t *mc)
+{
+ struct anxdp_softc * const sc = priv;
+
+ switch (mc->dev) {
+ case ANXDP_DAI_OUTPUT_MASTER_VOLUME:
+ case ANXDP_DAI_INPUT_DAC_VOLUME:
+ sc->sc_swvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
+ return 0;
+ default:
+ return ENXIO;
+ }
+}
+
+static int
+anxdp_audio_get_port(void *priv, mixer_ctrl_t *mc)
+{
+ struct anxdp_softc * const sc = priv;
+
+ switch (mc->dev) {
+ case ANXDP_DAI_OUTPUT_MASTER_VOLUME:
+ case ANXDP_DAI_INPUT_DAC_VOLUME:
+ mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = sc->sc_swvol;
+ mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = sc->sc_swvol;
+ return 0;
+ default:
+ return ENXIO;
+ }
+}
+
+static int
+anxdp_audio_query_devinfo(void *priv, mixer_devinfo_t *di)
+{
+ switch (di->index) {
+ case ANXDP_DAI_OUTPUT_CLASS:
+ di->mixer_class = di->index;
+ strcpy(di->label.name, AudioCoutputs);
+ di->type = AUDIO_MIXER_CLASS;
+ di->next = di->prev = AUDIO_MIXER_LAST;
+ return 0;
+
+ case ANXDP_DAI_INPUT_CLASS:
+ di->mixer_class = di->index;
+ strcpy(di->label.name, AudioCinputs);
+ di->type = AUDIO_MIXER_CLASS;
+ di->next = di->prev = AUDIO_MIXER_LAST;
+ return 0;
+
+ case ANXDP_DAI_OUTPUT_MASTER_VOLUME:
+ di->mixer_class = ANXDP_DAI_OUTPUT_CLASS;
+ strcpy(di->label.name, AudioNmaster);
+ di->un.v.delta = 1;
+ di->un.v.num_channels = 2;
+ strcpy(di->un.v.units.name, AudioNvolume);
+ di->type = AUDIO_MIXER_VALUE;
+ di->next = di->prev = AUDIO_MIXER_LAST;
+ return 0;
+
+ case ANXDP_DAI_INPUT_DAC_VOLUME:
+ di->mixer_class = ANXDP_DAI_INPUT_CLASS;
+ strcpy(di->label.name, AudioNdac);
+ di->un.v.delta = 1;
+ di->un.v.num_channels = 2;
+ strcpy(di->un.v.units.name, AudioNvolume);
+ di->type = AUDIO_MIXER_VALUE;
+ di->next = di->prev = AUDIO_MIXER_LAST;
+ return 0;
+
+ default:
+ return ENXIO;
+ }
+}
+
+static const struct audio_hw_if anxdp_dai_hw_if = {
+ .set_format = anxdp_audio_set_format,
+ .set_port = anxdp_audio_set_port,
+ .get_port = anxdp_audio_get_port,
+ .query_devinfo = anxdp_audio_query_devinfo,
+};
+#endif
+
+static ssize_t
+anxdp_dp_aux_transfer(struct drm_dp_aux *dpaux, struct drm_dp_aux_msg *dpmsg)
+{
+ struct anxdp_softc * const sc = container_of(dpaux, struct anxdp_softc,
+ sc_dpaux);
+ size_t loop_timeout = 0;
+ uint32_t val;
+ size_t i;
+ ssize_t ret = 0;
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_BUFFER_DATA_CTL,
+ BUF_CLR);
+
+ val = AUX_LENGTH(dpmsg->size);
+ if ((dpmsg->request & DP_AUX_I2C_MOT) != 0)
+ val |= AUX_TX_COMM_MOT;
+
+ switch (dpmsg->request & ~DP_AUX_I2C_MOT) {
+ case DP_AUX_I2C_WRITE:
+ break;
+ case DP_AUX_I2C_READ:
+ val |= AUX_TX_COMM_READ;
+ break;
+ case DP_AUX_NATIVE_WRITE:
+ val |= AUX_TX_COMM_DP;
+ break;
+ case DP_AUX_NATIVE_READ:
+ val |= AUX_TX_COMM_READ | AUX_TX_COMM_DP;
+ break;
+ }
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_CH_CTL_1, val);
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_ADDR_7_0,
+ AUX_ADDR_7_0(dpmsg->address));
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_ADDR_15_8,
+ AUX_ADDR_15_8(dpmsg->address));
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_ADDR_19_16,
+ AUX_ADDR_19_16(dpmsg->address));
+
+ if (!(dpmsg->request & DP_AUX_I2C_READ)) {
+ for (i = 0; i < dpmsg->size; i++) {
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh,
+ ANXDP_BUF_DATA(i),
+ ((const uint8_t *)(dpmsg->buffer))[i]);
+ ret++;
+ }
+ }
+
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_CH_CTL_2,
+ AUX_EN | ((dpmsg->size == 0) ? ADDR_ONLY : 0));
+
+ loop_timeout = 0;
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_CH_CTL_2);
+ while ((val & AUX_EN) != 0) {
+ if (++loop_timeout > 20000) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ delay(25);
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
+ ANXDP_AUX_CH_CTL_2);
+ }
+
+ loop_timeout = 0;
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_DP_INT_STA);
+ while (!(val & RPLY_RECEIV)) {
+ if (++loop_timeout > 2000) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ delay(10);
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
+ ANXDP_DP_INT_STA);
+ }
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_DP_INT_STA,
+ RPLY_RECEIV);
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_DP_INT_STA);
+ if ((val & AUX_ERR) != 0) {
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_DP_INT_STA,
+ AUX_ERR);
+ ret = -EREMOTEIO;
+ goto out;
+ }
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_CH_STA);
+ if (AUX_STATUS(val) != 0) {
+ ret = -EREMOTEIO;
+ goto out;
+ }
+
+ if ((dpmsg->request & DP_AUX_I2C_READ)) {
+ for (i = 0; i < dpmsg->size; i++) {
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
+ ANXDP_BUF_DATA(i));
+ ((uint8_t *)(dpmsg->buffer))[i] = val & 0xffU;
+ ret++;
+ }
+ }
+
+ val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_AUX_RX_COMM);
+ if (val == AUX_RX_COMM_AUX_DEFER)
+ dpmsg->reply = DP_AUX_NATIVE_REPLY_DEFER;
+ else if (val == AUX_RX_COMM_I2C_DEFER)
+ dpmsg->reply = DP_AUX_I2C_REPLY_DEFER;
+ else if ((dpmsg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE ||
+ (dpmsg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_READ)
+ dpmsg->reply = DP_AUX_I2C_REPLY_ACK;
+ else if ((dpmsg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_WRITE ||
+ (dpmsg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_READ)
+ dpmsg->reply = DP_AUX_NATIVE_REPLY_ACK;
+
+out:
+ if (ret < 0)
+ anxdp_init_aux(sc);
+
+ return ret;
+}
+
+void
+anxdp_dpms(struct anxdp_softc *sc, int mode)
+{
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ pmf_event_inject(NULL, PMFE_DISPLAY_ON);
+ break;
+ case DRM_MODE_DPMS_STANDBY:
+ case DRM_MODE_DPMS_SUSPEND:
+ case DRM_MODE_DPMS_OFF:
+ pmf_event_inject(NULL, PMFE_DISPLAY_OFF);
+ break;
+ }
+}
+
+int
+anxdp_attach(struct anxdp_softc *sc)
+{
+#if ANXDP_AUDIO
+ sc->sc_swvol = 255;
+
+ /*
+ * Initialize audio DAI
+ */
+ sc->sc_dai.dai_set_format = anxdp_dai_set_format;
+ sc->sc_dai.dai_add_device = anxdp_dai_add_device;
+ sc->sc_dai.dai_hw_if = &anxdp_dai_hw_if;
+ sc->sc_dai.dai_dev = sc->sc_dev;
+ sc->sc_dai.dai_priv = sc;
+#endif
+
+ sc->sc_dpaux.name = "DP Aux";
+ sc->sc_dpaux.transfer = anxdp_dp_aux_transfer;
+ sc->sc_dpaux.dev = sc->sc_dev;
+ if (drm_dp_aux_register(&sc->sc_dpaux) != 0) {
+ device_printf(sc->sc_dev, "registering DP Aux failed\n");
+ }
+
+ anxdp_bringup(sc);
+
+ return 0;
+}
+
+int
+anxdp_bind(struct anxdp_softc *sc, struct drm_encoder *encoder)
+{
+ int error;
+
+ sc->sc_bridge.driver_private = sc;
+ sc->sc_bridge.funcs = &anxdp_bridge_funcs;
+ sc->sc_bridge.encoder = encoder;
+
+ error = drm_bridge_attach(encoder->dev, &sc->sc_bridge);
+ if (error != 0)
+ return EIO;
+
+ encoder->bridge = &sc->sc_bridge;
+
+ if (sc->sc_panel != NULL && sc->sc_panel->funcs != NULL && sc->sc_panel->funcs->prepare != NULL)
+ sc->sc_panel->funcs->prepare(sc->sc_panel);
+
+ return 0;
+}
+
+void anxdp0_dump(void);
+
+void
+anxdp0_dump(void)
+{
+ extern struct cfdriver anxdp_cd;
+ struct anxdp_softc * const sc = device_lookup_private(&anxdp_cd, 0);
+ size_t i;
+
+ if (sc == NULL)
+ return;
+
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_1,
+ bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_1));
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_2,
+ bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_2));
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_3,
+ bus_space_read_4(sc->sc_bst, sc->sc_bsh, ANXDP_SYS_CTL_3));
+ for (i = 0x000; i < 0xb00; i += 4)
+ device_printf(sc->sc_dev, "%03zx 0x%08x\n", i,
+ bus_space_read_4(sc->sc_bst, sc->sc_bsh, i));
+}
diff --git a/sys/dev/ic/anx_dp.h b/sys/dev/ic/anx_dp.h
new file mode 100644
index 00000000000..db1674623e6
--- /dev/null
+++ b/sys/dev/ic/anx_dp.h
@@ -0,0 +1,79 @@
+/* $NetBSD: anx_dp.h,v 1.2.4.2 2020/01/21 10:39:58 martin Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jonathan A. Kollasch <jakllsch@kollasch.net>
+ * 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 ``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 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.
+ */
+
+#ifndef _DEV_IC_ANXDP_H
+#define _DEV_IC_ANXDP_H
+
+#if ANXDP_AUDIO
+#include <dev/audio/audio_dai.h>
+#endif
+
+#include <drm/drmP.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_panel.h>
+
+struct anxdp_softc;
+
+struct anxdp_connector {
+ struct drm_connector base;
+ struct anxdp_softc *sc;
+#if ANXDP_AUDIO
+
+ bool monitor_audio;
+#endif
+};
+
+struct anxdp_softc {
+ device_t sc_dev;
+ bus_space_tag_t sc_bst;
+ bus_space_handle_t sc_bsh;
+ u_int sc_flags;
+#define ANXDP_FLAG_ROCKCHIP __BIT(0)
+
+#if ANXDP_AUDIO
+ struct audio_dai_device sc_dai;
+ uint8_t sc_swvol;
+
+#endif
+ struct anxdp_connector sc_connector;
+ struct drm_bridge sc_bridge;
+ struct drm_dp_aux sc_dpaux;
+ struct drm_panel * sc_panel;
+ uint8_t sc_dpcd[DP_RECEIVER_CAP_SIZE];
+
+ struct drm_display_mode sc_curmode;
+};
+
+#define to_anxdp_connector(x) container_of(x, struct anxdp_connector, base)
+
+int anxdp_attach(struct anxdp_softc *);
+int anxdp_bind(struct anxdp_softc *, struct drm_encoder *);
+
+void anxdp_dpms(struct anxdp_softc *, int);
+
+#endif /* !_DEV_IC_ANXDP_H */
diff --git a/sys/dev/ic/dwc_mmc.c b/sys/dev/ic/dwc_mmc.c
index bca0a32b4df..566eb8b0a46 100644
--- a/sys/dev/ic/dwc_mmc.c
+++ b/sys/dev/ic/dwc_mmc.c
@@ -1,4 +1,4 @@
-/* $NetBSD: dwc_mmc.c,v 1.17 2019/07/28 10:30:44 jmcneill Exp $ */
+/* $NetBSD: dwc_mmc.c,v 1.17.2.1 2020/01/21 10:39:58 martin Exp $ */
/*-
* Copyright (c) 2014-2017 Jared McNeill <jmcneill@invisible.ca>
@@ -27,7 +27,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: dwc_mmc.c,v 1.17 2019/07/28 10:30:44 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: dwc_mmc.c,v 1.17.2.1 2020/01/21 10:39:58 martin Exp $");
#include <sys/param.h>
#include <sys/bus.h>
@@ -54,6 +54,7 @@ static int dwc_mmc_bus_power(sdmmc_chipset_handle_t, uint32_t);
static int dwc_mmc_bus_clock(sdmmc_chipset_handle_t, int);
static int dwc_mmc_bus_width(sdmmc_chipset_handle_t, int);
static int dwc_mmc_bus_rod(sdmmc_chipset_handle_t, int);
+static int dwc_mmc_signal_voltage(sdmmc_chipset_handle_t, int);
static void dwc_mmc_exec_command(sdmmc_chipset_handle_t,
struct sdmmc_command *);
static void dwc_mmc_card_enable_intr(sdmmc_chipset_handle_t, int);
@@ -69,6 +70,7 @@ static struct sdmmc_chip_functions dwc_mmc_chip_functions = {
.bus_clock = dwc_mmc_bus_clock,
.bus_width = dwc_mmc_bus_width,
.bus_rod = dwc_mmc_bus_rod,
+ .signal_voltage = dwc_mmc_signal_voltage,
.exec_command = dwc_mmc_exec_command,
.card_enable_intr = dwc_mmc_card_enable_intr,
.card_intr_ack = dwc_mmc_card_intr_ack,
@@ -79,16 +81,40 @@ static struct sdmmc_chip_functions dwc_mmc_chip_functions = {
#define MMC_READ(sc, reg) \
bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
-static void
-dwc_mmc_dump_regs(struct dwc_mmc_softc *sc)
+static int
+dwc_mmc_dmabounce_setup(struct dwc_mmc_softc *sc)
{
- device_printf(sc->sc_dev, "device registers:\n");
- for (u_int off = 0x00; off < 0x100; off += 16) {
- device_printf(sc->sc_dev, "xxxxxx%02x: %08x %08x %08x %08x\n",
- off,
- MMC_READ(sc, off + 0), MMC_READ(sc, off + 4),
- MMC_READ(sc, off + 8), MMC_READ(sc, off + 12));
- }
+ bus_dma_segment_t ds[1];
+ int error, rseg;
+
+ sc->sc_dmabounce_buflen = dwc_mmc_host_maxblklen(sc);
+ error = bus_dmamem_alloc(sc->sc_dmat, sc->sc_dmabounce_buflen, 0,
+ sc->sc_dmabounce_buflen, ds, 1, &rseg, BUS_DMA_WAITOK);
+ if (error)
+ return error;
+ error = bus_dmamem_map(sc->sc_dmat, ds, 1, sc->sc_dmabounce_buflen,
+ &sc->sc_dmabounce_buf, BUS_DMA_WAITOK);
+ if (error)
+ goto free;
+ error = bus_dmamap_create(sc->sc_dmat, sc->sc_dmabounce_buflen, 1,
+ sc->sc_dmabounce_buflen, 0, BUS_DMA_WAITOK, &sc->sc_dmabounce_map);
+ if (error)
+ goto unmap;
+ error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmabounce_map,
+ sc->sc_dmabounce_buf, sc->sc_dmabounce_buflen, NULL,
+ BUS_DMA_WAITOK);
+ if (error)
+ goto destroy;
+ return 0;
+
+destroy:
+ bus_dmamap_destroy(sc->sc_dmat, sc->sc_dmabounce_map);
+unmap:
+ bus_dmamem_unmap(sc->sc_dmat, sc->sc_dmabounce_buf,
+ sc->sc_dmabounce_buflen);
+free:
+ bus_dmamem_free(sc->sc_dmat, ds, rseg);
+ return error;
}
static int
@@ -136,64 +162,38 @@ dwc_mmc_attach_i(device_t self)
struct dwc_mmc_softc *sc = device_private(self);
struct sdmmcbus_attach_args saa;
+ if (sc->sc_pre_power_on)
+ sc->sc_pre_power_on(sc);
+
+ dwc_mmc_signal_voltage(sc, SDMMC_SIGNAL_VOLTAGE_330);
dwc_mmc_host_reset(sc);
dwc_mmc_bus_width(sc, 1);
+ if (sc->sc_post_power_on)
+ sc->sc_post_power_on(sc);
+
memset(&saa, 0, sizeof(saa));
saa.saa_busname = "sdmmc";
saa.saa_sct = &dwc_mmc_chip_functions;
saa.saa_sch = sc;
saa.saa_clkmin = 400;
saa.saa_clkmax = sc->sc_clock_freq / 1000;
- saa.saa_caps = SMC_CAPS_4BIT_MODE|
- SMC_CAPS_8BIT_MODE|
- SMC_CAPS_SD_HIGHSPEED|
- SMC_CAPS_MMC_HIGHSPEED|
- SMC_CAPS_AUTO_STOP;
- if (ISSET(sc->sc_flags, DWC_MMC_F_DMA)) {
- saa.saa_dmat = sc->sc_dmat;
- saa.saa_caps |= SMC_CAPS_DMA |
- SMC_CAPS_MULTI_SEG_DMA;
- }
+ saa.saa_dmat = sc->sc_dmat;
+ saa.saa_caps = SMC_CAPS_SD_HIGHSPEED |
+ SMC_CAPS_MMC_HIGHSPEED |
+ SMC_CAPS_AUTO_STOP |
+ SMC_CAPS_DMA |
+ SMC_CAPS_MULTI_SEG_DMA;
+ if (sc->sc_bus_width == 8)
+ saa.saa_caps |= SMC_CAPS_8BIT_MODE;
+ else
+ saa.saa_caps |= SMC_CAPS_4BIT_MODE;
if (sc->sc_card_detect)
saa.saa_caps |= SMC_CAPS_POLL_CARD_DET;
sc->sc_sdmmc_dev = config_found(self, &saa, NULL);
}
-static int
-dwc_mmc_wait_rint(struct dwc_mmc_softc *sc, uint32_t mask, int timeout)
-{
- const bool use_dma = ISSET(sc->sc_flags, DWC_MMC_F_DMA);
- int retry, error;
-
- KASSERT(mutex_owned(&sc->sc_intr_lock));
-
- if (sc->sc_intr_rint & mask)
- return 0;
-
- retry = timeout / hz;
-
- while (retry > 0) {
- if (use_dma) {
- error = cv_timedwait(&sc->sc_intr_cv,
- &sc->sc_intr_lock, hz);
- if (error && error != EWOULDBLOCK)
- return error;
- if (sc->sc_intr_rint & mask)
- return 0;
- } else {
- sc->sc_intr_rint |= MMC_READ(sc, DWC_MMC_RINT);
- if (sc->sc_intr_rint & mask)
- return 0;
- delay(1000);
- }
- --retry;
- }
-
- return ETIMEDOUT;
-}
-
static void
dwc_mmc_led(struct dwc_mmc_softc *sc, int on)
{
@@ -212,7 +212,16 @@ dwc_mmc_host_reset(sdmmc_chipset_handle_t sch)
aprint_normal_dev(sc->sc_dev, "host reset\n");
#endif
- MMC_WRITE(sc, DWC_MMC_PWREN, 1);
+ if (ISSET(sc->sc_flags, DWC_MMC_F_PWREN_INV))
+ MMC_WRITE(sc, DWC_MMC_PWREN, 0);
+ else
+ MMC_WRITE(sc, DWC_MMC_PWREN, 1);
+
+ ctrl = MMC_READ(sc, DWC_MMC_GCTRL);
+ ctrl &= ~DWC_MMC_GCTRL_USE_INTERNAL_DMAC;
+ MMC_WRITE(sc, DWC_MMC_GCTRL, ctrl);
+
+ MMC_WRITE(sc, DWC_MMC_DMAC, DWC_MMC_DMAC_SOFTRESET);
MMC_WRITE(sc, DWC_MMC_GCTRL,
MMC_READ(sc, DWC_MMC_GCTRL) | DWC_MMC_GCTRL_RESET);
@@ -226,9 +235,9 @@ dwc_mmc_host_reset(sdmmc_chipset_handle_t sch)
MMC_WRITE(sc, DWC_MMC_TIMEOUT, 0xffffffff);
- MMC_WRITE(sc, DWC_MMC_IMASK,
- DWC_MMC_INT_CMD_DONE | DWC_MMC_INT_ERROR |
- DWC_MMC_INT_DATA_OVER | DWC_MMC_INT_AUTO_CMD_DONE);
+ MMC_WRITE(sc, DWC_MMC_IMASK, 0);
+
+ MMC_WRITE(sc, DWC_MMC_RINT, 0xffffffff);
const uint32_t rx_wmark = (sc->sc_fifo_depth / 2) - 1;
const uint32_t tx_wmark = sc->sc_fifo_depth / 2;
@@ -291,6 +300,17 @@ dwc_mmc_bus_power(sdmmc_chipset_handle_t sch, uint32_t ocr)
}
static int
+dwc_mmc_signal_voltage(sdmmc_chipset_handle_t sch, int signal_voltage)
+{
+ struct dwc_mmc_softc *sc = sch;
+
+ if (sc->sc_signal_voltage == NULL)
+ return 0;
+
+ return sc->sc_signal_voltage(sc, signal_voltage);
+}
+
+static int
dwc_mmc_update_clock(struct dwc_mmc_softc *sc)
{
uint32_t cmd;
@@ -346,10 +366,12 @@ static int
dwc_mmc_set_clock(struct dwc_mmc_softc *sc, u_int freq)
{
const u_int pll_freq = sc->sc_clock_freq / 1000;
- u_int clk_div;
+ u_int clk_div, ciu_div;
+
+ ciu_div = sc->sc_ciu_div > 0 ? sc->sc_ciu_div : 1;
if (freq != pll_freq)
- clk_div = howmany(pll_freq, freq);
+ clk_div = howmany(pll_freq, freq * ciu_div);
else
clk_div = 0;
@@ -421,56 +443,43 @@ dwc_mmc_bus_rod(sdmmc_chipset_handle_t sch, int on)
return -1;
}
-
-static int
-dwc_mmc_pio_wait(struct dwc_mmc_softc *sc, struct sdmmc_command *cmd)
-{
- int retry = 0xfffff;
- uint32_t bit = (cmd->c_flags & SCF_CMD_READ) ?
- DWC_MMC_STATUS_FIFO_EMPTY : DWC_MMC_STATUS_FIFO_FULL;
-
- while (--retry > 0) {
- uint32_t status = MMC_READ(sc, DWC_MMC_STATUS);
- if (!(status & bit))
- return 0;
- delay(10);
- }
-
- return ETIMEDOUT;
-}
-
-static int
-dwc_mmc_pio_transfer(struct dwc_mmc_softc *sc, struct sdmmc_command *cmd)
-{
- uint32_t *datap = (uint32_t *)cmd->c_data;
- int i;
-
- for (i = 0; i < (cmd->c_resid >> 2); i++) {
- if (dwc_mmc_pio_wait(sc, cmd))
- return ETIMEDOUT;
- if (cmd->c_flags & SCF_CMD_READ) {
- datap[i] = MMC_READ(sc, sc->sc_fifo_reg);
- } else {
- MMC_WRITE(sc, sc->sc_fifo_reg, datap[i]);
- }
- }
-
- return 0;
-}
-
static int
dwc_mmc_dma_prepare(struct dwc_mmc_softc *sc, struct sdmmc_command *cmd)
{
struct dwc_mmc_idma_desc *dma = sc->sc_idma_desc;
bus_addr_t desc_paddr = sc->sc_idma_map->dm_segs[0].ds_addr;
+ bus_dmamap_t map;
bus_size_t off;
int desc, resid, seg;
uint32_t val;
+ /*
+ * If the command includs a dma map use it, otherwise we need to
+ * bounce. This can happen for SDIO IO_RW_EXTENDED (CMD53) commands.
+ */
+ if (cmd->c_dmamap) {
+ map = cmd->c_dmamap;
+ } else {
+ if (cmd->c_datalen > sc->sc_dmabounce_buflen)
+ return E2BIG;
+ map = sc->sc_dmabounce_map;
+
+ if (ISSET(cmd->c_flags, SCF_CMD_READ)) {
+ memset(sc->sc_dmabounce_buf, 0, cmd->c_datalen);
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmabounce_map,
+ 0, cmd->c_datalen, BUS_DMASYNC_PREREAD);
+ } else {
+ memcpy(sc->sc_dmabounce_buf, cmd->c_data,
+ cmd->c_datalen);
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmabounce_map,
+ 0, cmd->c_datalen, BUS_DMASYNC_PREWRITE);
+ }
+ }
+
desc = 0;
- for (seg = 0; seg < cmd->c_dmamap->dm_nsegs; seg++) {
- bus_addr_t paddr = cmd->c_dmamap->dm_segs[seg].ds_addr;
- bus_size_t len = cmd->c_dmamap->dm_segs[seg].ds_len;
+ for (seg = 0; seg < map->dm_nsegs; seg++) {
+ bus_addr_t paddr = map->dm_segs[seg].ds_addr;
+ bus_size_t len = map->dm_segs[seg].ds_len;
resid = uimin(len, cmd->c_resid);
off = 0;
while (resid > 0) {
@@ -480,13 +489,11 @@ dwc_mmc_dma_prepare(struct dwc_mmc_softc *sc, struct sdmmc_command *cmd)
dma[desc].dma_buf_size = htole32(len);
dma[desc].dma_buf_addr = htole32(paddr + off);
dma[desc].dma_config = htole32(
+ DWC_MMC_IDMA_CONFIG_CH |
DWC_MMC_IDMA_CONFIG_OWN);
cmd->c_resid -= len;
resid -= len;
off += len;
- dma[desc].dma_next = htole32(
- desc_paddr + ((desc+1) *
- sizeof(struct dwc_mmc_idma_desc)));
if (desc == 0) {
dma[desc].dma_config |= htole32(
DWC_MMC_IDMA_CONFIG_FD);
@@ -494,10 +501,15 @@ dwc_mmc_dma_prepare(struct dwc_mmc_softc *sc, struct sdmmc_command *cmd)
if (cmd->c_resid == 0) {
dma[desc].dma_config |= htole32(
DWC_MMC_IDMA_CONFIG_LD);
+ dma[desc].dma_config |= htole32(
+ DWC_MMC_IDMA_CONFIG_ER);
+ dma[desc].dma_next = 0;
} else {
dma[desc].dma_config |=
- htole32(DWC_MMC_IDMA_CONFIG_CH|
- DWC_MMC_IDMA_CONFIG_DIC);
+ htole32(DWC_MMC_IDMA_CONFIG_DIC);
+ dma[desc].dma_next = htole32(
+ desc_paddr + ((desc+1) *
+ sizeof(struct dwc_mmc_idma_desc)));
}
++desc;
}
@@ -512,34 +524,46 @@ dwc_mmc_dma_prepare(struct dwc_mmc_softc *sc, struct sdmmc_command *cmd)
bus_dmamap_sync(sc->sc_dmat, sc->sc_idma_map, 0,
sc->sc_idma_size, BUS_DMASYNC_PREWRITE);
- sc->sc_idma_idst = 0;
+ MMC_WRITE(sc, DWC_MMC_DLBA, desc_paddr);
val = MMC_READ(sc, DWC_MMC_GCTRL);
val |= DWC_MMC_GCTRL_DMAEN;
- val |= DWC_MMC_GCTRL_INTEN;
MMC_WRITE(sc, DWC_MMC_GCTRL, val);
val |= DWC_MMC_GCTRL_DMARESET;
MMC_WRITE(sc, DWC_MMC_GCTRL, val);
+
MMC_WRITE(sc, DWC_MMC_DMAC, DWC_MMC_DMAC_SOFTRESET);
- MMC_WRITE(sc, DWC_MMC_DMAC,
- DWC_MMC_DMAC_IDMA_ON|DWC_MMC_DMAC_FIX_BURST);
- val = MMC_READ(sc, DWC_MMC_IDIE);
- val &= ~(DWC_MMC_IDST_RECEIVE_INT|DWC_MMC_IDST_TRANSMIT_INT);
if (cmd->c_flags & SCF_CMD_READ)
- val |= DWC_MMC_IDST_RECEIVE_INT;
+ val = DWC_MMC_IDST_RECEIVE_INT;
else
- val |= DWC_MMC_IDST_TRANSMIT_INT;
+ val = 0;
MMC_WRITE(sc, DWC_MMC_IDIE, val);
- MMC_WRITE(sc, DWC_MMC_DLBA, desc_paddr);
+ MMC_WRITE(sc, DWC_MMC_DMAC,
+ DWC_MMC_DMAC_IDMA_ON|DWC_MMC_DMAC_FIX_BURST);
return 0;
}
static void
-dwc_mmc_dma_complete(struct dwc_mmc_softc *sc)
+dwc_mmc_dma_complete(struct dwc_mmc_softc *sc, struct sdmmc_command *cmd)
{
+ MMC_WRITE(sc, DWC_MMC_DMAC, 0);
+ MMC_WRITE(sc, DWC_MMC_IDIE, 0);
+
bus_dmamap_sync(sc->sc_dmat, sc->sc_idma_map, 0,
sc->sc_idma_size, BUS_DMASYNC_POSTWRITE);
+
+ if (cmd->c_dmamap == NULL) {
+ if (ISSET(cmd->c_flags, SCF_CMD_READ)) {
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmabounce_map,
+ 0, cmd->c_datalen, BUS_DMASYNC_POSTREAD);
+ memcpy(cmd->c_data, sc->sc_dmabounce_buf,
+ cmd->c_datalen);
+ } else {
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmabounce_map,
+ 0, cmd->c_datalen, BUS_DMASYNC_POSTWRITE);
+ }
+ }
}
static void
@@ -547,7 +571,8 @@ dwc_mmc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
{
struct dwc_mmc_softc *sc = sch;
uint32_t cmdval = DWC_MMC_CMD_START;
- int retry = 200000;
+ int retry, error;
+ uint32_t imask;
#ifdef DWC_MMC_DEBUG
aprint_normal_dev(sc->sc_dev,
@@ -557,21 +582,17 @@ dwc_mmc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
#endif
mutex_enter(&sc->sc_intr_lock);
-
- do {
- const uint32_t status = MMC_READ(sc, DWC_MMC_STATUS);
- if ((status & DWC_MMC_STATUS_CARD_DATA_BUSY) == 0)
- break;
- delay(10);
- } while (--retry > 0);
- if (retry == 0) {
- aprint_error_dev(sc->sc_dev, "timeout waiting for data busy\n");
- cmd->c_error = ETIMEDOUT;
- goto done;
+ if (sc->sc_curcmd != NULL) {
+ device_printf(sc->sc_dev,
+ "WARNING: driver submitted a command while the controller was busy\n");
+ cmd->c_error = EBUSY;
+ SET(cmd->c_flags, SCF_ITSDONE);
+ mutex_exit(&sc->sc_intr_lock);
+ return;
}
+ sc->sc_curcmd = cmd;
MMC_WRITE(sc, DWC_MMC_IDST, 0xffffffff);
- MMC_WRITE(sc, DWC_MMC_RINT, 0xffffffff);
if (ISSET(sc->sc_flags, DWC_MMC_F_USE_HOLD_REG))
cmdval |= DWC_MMC_CMD_USE_HOLD_REG;
@@ -585,6 +606,8 @@ dwc_mmc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
if (cmd->c_flags & SCF_RSP_CRC)
cmdval |= DWC_MMC_CMD_CHECK_RSP_CRC;
+ imask = DWC_MMC_INT_ERROR | DWC_MMC_INT_CMD_DONE;
+
if (cmd->c_datalen > 0) {
unsigned int nblks;
@@ -599,13 +622,17 @@ dwc_mmc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
if (nblks > 1) {
cmdval |= DWC_MMC_CMD_SEND_AUTO_STOP;
+ imask |= DWC_MMC_INT_AUTO_CMD_DONE;
+ } else {
+ imask |= DWC_MMC_INT_DATA_OVER;
}
MMC_WRITE(sc, DWC_MMC_BLKSZ, cmd->c_blklen);
MMC_WRITE(sc, DWC_MMC_BYTECNT, nblks * cmd->c_blklen);
}
- sc->sc_intr_rint = 0;
+ MMC_WRITE(sc, DWC_MMC_IMASK, imask | sc->sc_intr_card);
+ MMC_WRITE(sc, DWC_MMC_RINT, 0x7fff);
MMC_WRITE(sc, DWC_MMC_ARG, cmd->c_arg);
@@ -613,89 +640,45 @@ dwc_mmc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
aprint_normal_dev(sc->sc_dev, "cmdval = %08x\n", cmdval);
#endif
+ cmd->c_resid = cmd->c_datalen;
if (cmd->c_datalen > 0) {
- cmd->c_resid = cmd->c_datalen;
dwc_mmc_led(sc, 0);
- if (ISSET(sc->sc_flags, DWC_MMC_F_DMA)) {
- cmd->c_error = dwc_mmc_dma_prepare(sc, cmd);
- MMC_WRITE(sc, DWC_MMC_CMD, cmdval | cmd->c_opcode);
- MMC_WRITE(sc, DWC_MMC_PLDMND, 1);
- if (cmd->c_error == 0) {
- const uint32_t idst_mask =
- DWC_MMC_IDST_ERROR | DWC_MMC_IDST_COMPLETE;
- retry = 10;
- while ((sc->sc_idma_idst & idst_mask) == 0) {
- if (--retry == 0) {
- cmd->c_error = ETIMEDOUT;
- break;
- }
- cv_timedwait(&sc->sc_idst_cv,
- &sc->sc_intr_lock, hz);
- }
- }
- dwc_mmc_dma_complete(sc);
- if (sc->sc_idma_idst & DWC_MMC_IDST_ERROR) {
- cmd->c_error = EIO;
- } else if (!(sc->sc_idma_idst & DWC_MMC_IDST_COMPLETE)) {
- cmd->c_error = ETIMEDOUT;
- }
- } else {
- mutex_exit(&sc->sc_intr_lock);
- MMC_WRITE(sc, DWC_MMC_CMD, cmdval | cmd->c_opcode);
- cmd->c_error = dwc_mmc_pio_transfer(sc, cmd);
- mutex_enter(&sc->sc_intr_lock);
- }
- dwc_mmc_led(sc, 1);
- if (cmd->c_error) {
-#ifdef DWC_MMC_DEBUG
- aprint_error_dev(sc->sc_dev,
- "xfer failed, error %d\n", cmd->c_error);
-#endif
+ cmd->c_error = dwc_mmc_dma_prepare(sc, cmd);
+ if (cmd->c_error != 0) {
+ SET(cmd->c_flags, SCF_ITSDONE);
goto done;
}
+ sc->sc_wait_dma = ISSET(cmd->c_flags, SCF_CMD_READ);
+ sc->sc_wait_data = true;
} else {
- MMC_WRITE(sc, DWC_MMC_CMD, cmdval | cmd->c_opcode);
+ sc->sc_wait_dma = false;
+ sc->sc_wait_data = false;
}
+ sc->sc_wait_cmd = true;
- cmd->c_error = dwc_mmc_wait_rint(sc,
- DWC_MMC_INT_ERROR|DWC_MMC_INT_CMD_DONE, hz * 10);
- if (cmd->c_error == 0 && (sc->sc_intr_rint & DWC_MMC_INT_ERROR)) {
- if (sc->sc_intr_rint & DWC_MMC_INT_RESP_TIMEOUT) {
- cmd->c_error = ETIMEDOUT;
- } else {
- cmd->c_error = EIO;
- }
- }
- if (cmd->c_error) {
-#ifdef DWC_MMC_DEBUG
- aprint_error_dev(sc->sc_dev,
- "cmd failed, error %d\n", cmd->c_error);
-#endif
- goto done;
- }
+ MMC_WRITE(sc, DWC_MMC_CMD, cmdval | cmd->c_opcode);
- if (cmd->c_datalen > 0) {
- cmd->c_error = dwc_mmc_wait_rint(sc,
- DWC_MMC_INT_ERROR|
- DWC_MMC_INT_AUTO_CMD_DONE|
- DWC_MMC_INT_DATA_OVER,
- hz*10);
- if (cmd->c_error == 0 &&
- (sc->sc_intr_rint & DWC_MMC_INT_ERROR)) {
- cmd->c_error = ETIMEDOUT;
- }
- if (cmd->c_error) {
-#ifdef DWC_MMC_DEBUG
- aprint_error_dev(sc->sc_dev,
- "data timeout, rint = %08x\n",
- sc->sc_intr_rint);
-#endif
- dwc_mmc_dump_regs(sc);
- cmd->c_error = ETIMEDOUT;
+ if (sc->sc_wait_dma)
+ MMC_WRITE(sc, DWC_MMC_PLDMND, 1);
+
+ struct bintime timeout = { .sec = 15, .frac = 0 };
+ const struct bintime epsilon = { .sec = 1, .frac = 0 };
+ while (!ISSET(cmd->c_flags, SCF_ITSDONE)) {
+ error = cv_timedwaitbt(&sc->sc_intr_cv,
+ &sc->sc_intr_lock, &timeout, &epsilon);
+ if (error != 0) {
+ cmd->c_error = error;
+ SET(cmd->c_flags, SCF_ITSDONE);
goto done;
}
}
+ if (cmd->c_error == 0 && cmd->c_datalen > 0)
+ dwc_mmc_dma_complete(sc, cmd);
+
+ if (cmd->c_datalen > 0)
+ dwc_mmc_led(sc, 1);
+
if (cmd->c_flags & SCF_RSP_PRESENT) {
if (cmd->c_flags & SCF_RSP_136) {
cmd->c_resp[0] = MMC_READ(sc, DWC_MMC_RESP0);
@@ -717,7 +700,12 @@ dwc_mmc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
}
done:
- cmd->c_flags |= SCF_ITSDONE;
+ KASSERT(ISSET(cmd->c_flags, SCF_ITSDONE));
+ MMC_WRITE(sc, DWC_MMC_IMASK, sc->sc_intr_card);
+ MMC_WRITE(sc, DWC_MMC_IDIE, 0);
+ MMC_WRITE(sc, DWC_MMC_RINT, 0x7fff);
+ MMC_WRITE(sc, DWC_MMC_IDST, 0xffffffff);
+ sc->sc_curcmd = NULL;
mutex_exit(&sc->sc_intr_lock);
if (cmd->c_error) {
@@ -735,20 +723,37 @@ done:
dwc_mmc_update_clock(sc);
}
- if (!ISSET(sc->sc_flags, DWC_MMC_F_DMA)) {
- MMC_WRITE(sc, DWC_MMC_GCTRL,
- MMC_READ(sc, DWC_MMC_GCTRL) | DWC_MMC_GCTRL_FIFORESET);
- }
+ MMC_WRITE(sc, DWC_MMC_GCTRL,
+ MMC_READ(sc, DWC_MMC_GCTRL) | DWC_MMC_GCTRL_FIFORESET);
}
static void
dwc_mmc_card_enable_intr(sdmmc_chipset_handle_t sch, int enable)
{
+ struct dwc_mmc_softc *sc = sch;
+ uint32_t imask;
+
+ mutex_enter(&sc->sc_intr_lock);
+ imask = MMC_READ(sc, DWC_MMC_IMASK);
+ if (enable)
+ imask |= DWC_MMC_INT_SDIO_INT;
+ else
+ imask &= ~DWC_MMC_INT_SDIO_INT;
+ sc->sc_intr_card = imask & DWC_MMC_INT_SDIO_INT;
+ MMC_WRITE(sc, DWC_MMC_IMASK, imask);
+ mutex_exit(&sc->sc_intr_lock);
}
static void
dwc_mmc_card_intr_ack(sdmmc_chipset_handle_t sch)
{
+ struct dwc_mmc_softc *sc = sch;
+ uint32_t imask;
+
+ mutex_enter(&sc->sc_intr_lock);
+ imask = MMC_READ(sc, DWC_MMC_IMASK);
+ MMC_WRITE(sc, DWC_MMC_IMASK, imask | sc->sc_intr_card);
+ mutex_exit(&sc->sc_intr_lock);
}
int
@@ -773,14 +778,9 @@ dwc_mmc_init(struct dwc_mmc_softc *sc)
mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_BIO);
cv_init(&sc->sc_intr_cv, "dwcmmcirq");
- cv_init(&sc->sc_idst_cv, "dwcmmcdma");
- const bool use_dma = ISSET(sc->sc_flags, DWC_MMC_F_DMA);
-
- aprint_debug_dev(sc->sc_dev, "using %s for transfers\n",
- use_dma ? "DMA" : "PIO");
-
- if (use_dma && dwc_mmc_idma_setup(sc) != 0) {
+ if (dwc_mmc_dmabounce_setup(sc) != 0 ||
+ dwc_mmc_idma_setup(sc) != 0) {
aprint_error_dev(sc->sc_dev, "failed to setup DMA\n");
return ENOMEM;
}
@@ -794,32 +794,86 @@ int
dwc_mmc_intr(void *priv)
{
struct dwc_mmc_softc *sc = priv;
- uint32_t idst, rint, mint;
+ struct sdmmc_command *cmd;
+ uint32_t idst, mint, imask;
mutex_enter(&sc->sc_intr_lock);
idst = MMC_READ(sc, DWC_MMC_IDST);
- rint = MMC_READ(sc, DWC_MMC_RINT);
mint = MMC_READ(sc, DWC_MMC_MINT);
- if (!idst && !rint && !mint) {
+ if (!idst && !mint) {
mutex_exit(&sc->sc_intr_lock);
return 0;
}
MMC_WRITE(sc, DWC_MMC_IDST, idst);
- MMC_WRITE(sc, DWC_MMC_RINT, rint);
- MMC_WRITE(sc, DWC_MMC_MINT, mint);
+ MMC_WRITE(sc, DWC_MMC_RINT, mint);
+
+ cmd = sc->sc_curcmd;
#ifdef DWC_MMC_DEBUG
- device_printf(sc->sc_dev, "mmc intr idst=%08X rint=%08X mint=%08X\n",
- idst, rint, mint);
+ device_printf(sc->sc_dev, "mmc intr idst=%08X mint=%08X\n",
+ idst, mint);
#endif
- if (idst) {
- sc->sc_idma_idst |= idst;
- cv_broadcast(&sc->sc_idst_cv);
+ /* Handle SDIO card interrupt */
+ if ((mint & DWC_MMC_INT_SDIO_INT) != 0) {
+ imask = MMC_READ(sc, DWC_MMC_IMASK);
+ MMC_WRITE(sc, DWC_MMC_IMASK, imask & ~DWC_MMC_INT_SDIO_INT);
+ sdmmc_card_intr(sc->sc_sdmmc_dev);
+ }
+
+ /* Error interrupts take priority over command and transfer interrupts */
+ if (cmd != NULL && (mint & DWC_MMC_INT_ERROR) != 0) {
+ imask = MMC_READ(sc, DWC_MMC_IMASK);
+ MMC_WRITE(sc, DWC_MMC_IMASK, imask & ~DWC_MMC_INT_ERROR);
+ if ((mint & DWC_MMC_INT_RESP_TIMEOUT) != 0) {
+ cmd->c_error = ETIMEDOUT;
+ /* Wait for command to complete */
+ sc->sc_wait_data = sc->sc_wait_dma = false;
+ if (cmd->c_opcode != SD_IO_SEND_OP_COND &&
+ cmd->c_opcode != SD_IO_RW_DIRECT &&
+ !ISSET(cmd->c_flags, SCF_TOUT_OK))
+ device_printf(sc->sc_dev, "host controller timeout, mint=0x%08x\n", mint);
+ } else {
+ device_printf(sc->sc_dev, "host controller error, mint=0x%08x\n", mint);
+ cmd->c_error = EIO;
+ SET(cmd->c_flags, SCF_ITSDONE);
+ goto done;
+ }
}
- if (rint) {
- sc->sc_intr_rint |= rint;
+ if (cmd != NULL && (idst & DWC_MMC_IDST_RECEIVE_INT) != 0) {
+ MMC_WRITE(sc, DWC_MMC_IDIE, 0);
+ if (sc->sc_wait_dma == false)
+ device_printf(sc->sc_dev, "unexpected DMA receive interrupt\n");
+ sc->sc_wait_dma = false;
+ }
+
+ if (cmd != NULL && (mint & DWC_MMC_INT_CMD_DONE) != 0) {
+ imask = MMC_READ(sc, DWC_MMC_IMASK);
+ MMC_WRITE(sc, DWC_MMC_IMASK, imask & ~DWC_MMC_INT_CMD_DONE);
+ if (sc->sc_wait_cmd == false)
+ device_printf(sc->sc_dev, "unexpected command complete interrupt\n");
+ sc->sc_wait_cmd = false;
+ }
+
+ const uint32_t dmadone_mask = DWC_MMC_INT_AUTO_CMD_DONE|DWC_MMC_INT_DATA_OVER;
+ if (cmd != NULL && (mint & dmadone_mask) != 0) {
+ imask = MMC_READ(sc, DWC_MMC_IMASK);
+ MMC_WRITE(sc, DWC_MMC_IMASK, imask & ~dmadone_mask);
+ if (sc->sc_wait_data == false)
+ device_printf(sc->sc_dev, "unexpected data complete interrupt\n");
+ sc->sc_wait_data = false;
+ }
+
+ if (cmd != NULL &&
+ sc->sc_wait_dma == false &&
+ sc->sc_wait_cmd == false &&
+ sc->sc_wait_data == false) {
+ SET(cmd->c_flags, SCF_ITSDONE);
+ }
+
+done:
+ if (cmd != NULL && ISSET(cmd->c_flags, SCF_ITSDONE)) {
cv_broadcast(&sc->sc_intr_cv);
}
diff --git a/sys/dev/ic/dwc_mmc_var.h b/sys/dev/ic/dwc_mmc_var.h
index dae8c139af9..22ed689233f 100644
--- a/sys/dev/ic/dwc_mmc_var.h
+++ b/sys/dev/ic/dwc_mmc_var.h
@@ -1,4 +1,4 @@
-/* $NetBSD: dwc_mmc_var.h,v 1.8 2019/04/30 23:19:55 jmcneill Exp $ */
+/* $NetBSD: dwc_mmc_var.h,v 1.8.2.1 2020/01/21 10:39:58 martin Exp $ */
/*-
* Copyright (c) 2014-2017 Jared McNeill <jmcneill@invisible.ca>
@@ -39,18 +39,21 @@ struct dwc_mmc_softc {
u_int sc_flags;
#define DWC_MMC_F_DMA __BIT(0)
#define DWC_MMC_F_USE_HOLD_REG __BIT(1)
+#define DWC_MMC_F_PWREN_INV __BIT(2)
uint32_t sc_fifo_reg;
uint32_t sc_fifo_depth;
u_int sc_clock_freq;
+ u_int sc_bus_width;
void *sc_ih;
kmutex_t sc_intr_lock;
kcondvar_t sc_intr_cv;
- kcondvar_t sc_idst_cv;
int sc_mmc_width;
int sc_mmc_port;
+ u_int sc_ciu_div;
+
device_t sc_sdmmc_dev;
uint32_t sc_idma_xferlen;
@@ -61,14 +64,24 @@ struct dwc_mmc_softc {
int sc_idma_ndesc;
void *sc_idma_desc;
- uint32_t sc_intr_rint;
- uint32_t sc_intr_mint;
- uint32_t sc_idma_idst;
+ bus_dmamap_t sc_dmabounce_map;
+ void *sc_dmabounce_buf;
+ size_t sc_dmabounce_buflen;
+
+ uint32_t sc_intr_card;
+ struct sdmmc_command *sc_curcmd;
+ bool sc_wait_dma;
+ bool sc_wait_cmd;
+ bool sc_wait_data;
+
+ void (*sc_pre_power_on)(struct dwc_mmc_softc *);
+ void (*sc_post_power_on)(struct dwc_mmc_softc *);
int (*sc_card_detect)(struct dwc_mmc_softc *);
int (*sc_write_protect)(struct dwc_mmc_softc *);
void (*sc_set_led)(struct dwc_mmc_softc *, int);
int (*sc_bus_clock)(struct dwc_mmc_softc *, int);
+ int (*sc_signal_voltage)(struct dwc_mmc_softc *, int);
};
int dwc_mmc_init(struct dwc_mmc_softc *);
diff --git a/sys/dev/usb/ums.c b/sys/dev/usb/ums.c
index ab9fe4be6be..f9f9fa7c314 100644
--- a/sys/dev/usb/ums.c
+++ b/sys/dev/usb/ums.c
@@ -1,4 +1,4 @@
-/* $NetBSD: ums.c,v 1.93 2019/05/05 03:17:54 mrg Exp $ */
+/* $NetBSD: ums.c,v 1.93.2.1 2020/01/21 10:39:59 martin Exp $ */
/*
* Copyright (c) 1998, 2017 The NetBSD Foundation, Inc.
@@ -35,7 +35,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ums.c,v 1.93 2019/05/05 03:17:54 mrg Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ums.c,v 1.93.2.1 2020/01/21 10:39:59 martin Exp $");
#ifdef _KERNEL_OPT
#include "opt_usb.h"
@@ -78,6 +78,12 @@ struct ums_softc {
struct uhidev sc_hdev;
struct hidms sc_ms;
+ bool sc_alwayson;
+
+ bool sc_alwayson;
+
+ bool sc_alwayson;
+
int sc_enabled;
char sc_dying;
};
@@ -107,7 +113,7 @@ int
ums_match(device_t parent, cfdata_t match, void *aux)
{
struct uhidev_attach_arg *uha = aux;
- int size;
+ int size, error;
void *desc;
/*
@@ -135,7 +141,7 @@ ums_attach(device_t parent, device_t self, void *aux)
{
struct ums_softc *sc = device_private(self);
struct uhidev_attach_arg *uha = aux;
- int size;
+ int size, error;
void *desc;
uint32_t quirks;
@@ -192,7 +198,25 @@ ums_attach(device_t parent, device_t self, void *aux)
}
}
+ if (uha->uiaa->uiaa_vendor == USB_VENDOR_HAILUCK &&
+ uha->uiaa->uiaa_product == USB_PRODUCT_HAILUCK_KEYBOARD) {
+ /*
+ * The HAILUCK USB Keyboard has a built-in touchpad, which
+ * needs to be active for the keyboard to function properly.
+ */
+ sc->sc_alwayson = true;
+ }
+
hidms_attach(self, &sc->sc_ms, &ums_accessops);
+
+ if (sc->sc_alwayson) {
+ error = uhidev_open(&sc->sc_hdev);
+ if (error != 0) {
+ aprint_error_dev(self,
+ "WARNING: couldn't open always-on device\n");
+ sc->sc_alwayson = false;
+ }
+ }
}
int
@@ -226,6 +250,15 @@ ums_detach(device_t self, int flags)
DPRINTF(("ums_detach: sc=%p flags=%d\n", sc, flags));
+ if (sc->sc_alwayson)
+ uhidev_close(&sc->sc_hdev);
+
+ if (sc->sc_alwayson)
+ uhidev_close(&sc->sc_hdev);
+
+ if (sc->sc_alwayson)
+ uhidev_close(&sc->sc_hdev);
+
/* No need to do reference counting of ums, wsmouse has all the goo. */
if (sc->sc_ms.hidms_wsmousedev != NULL)
rv = config_detach(sc->sc_ms.hidms_wsmousedev, flags);
@@ -239,14 +272,16 @@ void
ums_intr(struct uhidev *addr, void *ibuf, u_int len)
{
struct ums_softc *sc = (struct ums_softc *)addr;
- hidms_intr(&sc->sc_ms, ibuf, len);
+
+ if (sc->sc_enabled)
+ hidms_intr(&sc->sc_ms, ibuf, len);
}
Static int
ums_enable(void *v)
{
struct ums_softc *sc = v;
- int error;
+ int error = 0;
DPRINTFN(1,("ums_enable: sc=%p\n", sc));
@@ -259,9 +294,11 @@ ums_enable(void *v)
sc->sc_enabled = 1;
sc->sc_ms.hidms_buttons = 0;
- error = uhidev_open(&sc->sc_hdev);
- if (error)
- sc->sc_enabled = 0;
+ if (!sc->sc_alwayson) {
+ error = uhidev_open(&sc->sc_hdev);
+ if (error)
+ sc->sc_enabled = 0;
+ }
return error;
}
@@ -281,7 +318,8 @@ ums_disable(void *v)
if (sc->sc_enabled) {
sc->sc_enabled = 0;
- uhidev_close(&sc->sc_hdev);
+ if (!sc->sc_alwayson)
+ uhidev_close(&sc->sc_hdev);
}
}
diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs
index 67461337f56..9f809cae228 100644
--- a/sys/dev/usb/usbdevs
+++ b/sys/dev/usb/usbdevs
@@ -1,4 +1,4 @@
-$NetBSD: usbdevs,v 1.770.4.1 2019/08/26 13:54:41 martin Exp $
+$NetBSD: usbdevs,v 1.770.4.2 2020/01/21 10:39:59 martin Exp $
/*-
* Copyright (c) 1998-2004 The NetBSD Foundation, Inc.
@@ -577,6 +577,7 @@ vendor ARDUINO 0x2341 Arduino SA
vendor TPLINK 0x2357 TP-Link
vendor WMR 0x2405 West Mountain Radio
vendor TRIPPLITE 0x2478 Tripp-Lite
+vendor HAILUCK 0x258a HAILUCK Co., Ltd
vendor HIROSE 0x2631 Hirose Electric
vendor NHJ 0x2770 NHJ
vendor PLANEX 0x2c02 Planex Communications
@@ -1732,6 +1733,9 @@ product HAGIWARA FGSM 0x0002 FlashGate SmartMedia Card Reader
product HAGIWARA FGCF 0x0003 FlashGate CompactFlash Card Reader
product HAGIWARA FG 0x0005 FlashGate
+/* HAILUCK Co., Ltd products */
+product HAILUCK KEYBOARD 0x001e Keyboard
+
/* HAL Corporation products */
product HAL IMR001 0x0011 Crossam2+USB IR commander