diff --git a/sys/arm/ti/am335x/am335x_dmtpps.c b/sys/arm/ti/am335x/am335x_dmtpps.c index 414800fd011e..24d83f248eef 100644 --- a/sys/arm/ti/am335x/am335x_dmtpps.c +++ b/sys/arm/ti/am335x/am335x_dmtpps.c @@ -1,621 +1,621 @@ /*- * Copyright (c) 2015 Ian lepore * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * AM335x PPS driver using DMTimer capture. * * Note that this PPS driver does not use an interrupt. Instead it uses the * hardware's ability to latch the timer's count register in response to a * signal on an IO pin. Each of timers 4-7 have an associated pin, and this * code allows any one of those to be used. * * The timecounter routines in kern_tc.c call the pps poll routine periodically * to see if a new counter value has been latched. When a new value has been * latched, the only processing done in the poll routine is to capture the * current set of timecounter timehands (done with pps_capture()) and the * latched value from the timer. The remaining work (done by pps_event() while * holding a mutex) is scheduled to be done later in a non-interrupt context. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "am335x_dmtreg.h" #define PPS_CDEV_NAME "dmtpps" struct dmtpps_softc { device_t dev; int mem_rid; struct resource * mem_res; int tmr_num; /* N from hwmod str "timerN" */ char tmr_name[12]; /* "DMTimerN" */ uint32_t tclr; /* Cached TCLR register. */ struct timecounter tc; int pps_curmode; /* Edge mode now set in hw. */ struct cdev * pps_cdev; struct pps_state pps_state; struct mtx pps_mtx; clk_t clk_fck; uint64_t sysclk_freq; }; static int dmtpps_tmr_num; /* Set by probe() */ /* List of compatible strings for FDT tree */ static struct ofw_compat_data compat_data[] = { {"ti,am335x-timer", 1}, {"ti,am335x-timer-1ms", 1}, {NULL, 0}, }; +SIMPLEBUS_PNP_INFO(compat_data); /* * A table relating pad names to the hardware timer number they can be mux'd to. */ struct padinfo { char * ballname; int tmr_num; }; static struct padinfo dmtpps_padinfo[] = { {"GPMC_ADVn_ALE", 4}, {"I2C0_SDA", 4}, {"MII1_TX_EN", 4}, {"XDMA_EVENT_INTR0", 4}, {"GPMC_BEn0_CLE", 5}, {"MDC", 5}, {"MMC0_DAT3", 5}, {"UART1_RTSn", 5}, {"GPMC_WEn", 6}, {"MDIO", 6}, {"MMC0_DAT2", 6}, {"UART1_CTSn", 6}, {"GPMC_OEn_REn", 7}, {"I2C0_SCL", 7}, {"UART0_CTSn", 7}, {"XDMA_EVENT_INTR1", 7}, {NULL, 0} }; /* * This is either brilliantly user-friendly, or utterly lame... * * The am335x chip is used on the popular Beaglebone boards. Those boards have * pins for all four capture-capable timers available on the P8 header. Allow * users to configure the input pin by giving the name of the header pin. */ struct nicknames { const char * nick; const char * name; }; static struct nicknames dmtpps_pin_nicks[] = { {"P8-7", "GPMC_ADVn_ALE"}, {"P8-9", "GPMC_BEn0_CLE"}, {"P8-10", "GPMC_WEn"}, {"P8-8", "GPMC_OEn_REn",}, {NULL, NULL} }; #define DMTIMER_READ4(sc, reg) bus_read_4((sc)->mem_res, (reg)) #define DMTIMER_WRITE4(sc, reg, val) bus_write_4((sc)->mem_res, (reg), (val)) /* * Translate a short friendly case-insensitive name to its canonical name. */ static const char * dmtpps_translate_nickname(const char *nick) { struct nicknames *nn; for (nn = dmtpps_pin_nicks; nn->nick != NULL; nn++) if (strcasecmp(nick, nn->nick) == 0) return nn->name; return (nick); } /* * See if our tunable is set to the name of the input pin. If not, that's NOT * an error, return 0. If so, try to configure that pin as a timer capture * input pin, and if that works, then we have our timer unit number and if it * fails that IS an error, return -1. */ static int dmtpps_find_tmr_num_by_tunable(void) { struct padinfo *pi; char iname[20]; char muxmode[12]; const char * ballname; int err; if (!TUNABLE_STR_FETCH("hw.am335x_dmtpps.input", iname, sizeof(iname))) return (0); ballname = dmtpps_translate_nickname(iname); for (pi = dmtpps_padinfo; pi->ballname != NULL; pi++) { if (strcmp(ballname, pi->ballname) != 0) continue; snprintf(muxmode, sizeof(muxmode), "timer%d", pi->tmr_num); err = ti_pinmux_padconf_set(pi->ballname, muxmode, PADCONF_INPUT); if (err != 0) { printf("am335x_dmtpps: unable to configure capture pin " "for %s to input mode\n", muxmode); return (-1); } else if (bootverbose) { printf("am335x_dmtpps: configured pin %s as input " "for %s\n", iname, muxmode); } return (pi->tmr_num); } /* Invalid name in the tunable, that's an error. */ printf("am335x_dmtpps: unknown pin name '%s'\n", iname); return (-1); } /* * Ask the pinmux driver whether any pin has been configured as a TIMER4..TIMER7 * input pin. If so, return the timer number, if not return 0. */ static int dmtpps_find_tmr_num_by_padconf(void) { int err; unsigned int padstate; const char * padmux; struct padinfo *pi; char muxmode[12]; for (pi = dmtpps_padinfo; pi->ballname != NULL; pi++) { err = ti_pinmux_padconf_get(pi->ballname, &padmux, &padstate); snprintf(muxmode, sizeof(muxmode), "timer%d", pi->tmr_num); if (err == 0 && (padstate & RXACTIVE) != 0 && strcmp(muxmode, padmux) == 0) return (pi->tmr_num); } /* Nothing found, not an error. */ return (0); } /* * Figure out which hardware timer number to use based on input pin * configuration. This is done just once, the first time probe() runs. */ static int dmtpps_find_tmr_num(void) { int tmr_num; if ((tmr_num = dmtpps_find_tmr_num_by_tunable()) == 0) tmr_num = dmtpps_find_tmr_num_by_padconf(); if (tmr_num <= 0) { printf("am335x_dmtpps: PPS driver not enabled: unable to find " "or configure a capture input pin\n"); tmr_num = -1; /* Must return non-zero to prevent re-probing. */ } return (tmr_num); } static void dmtpps_set_hw_capture(struct dmtpps_softc *sc, bool force_off) { int newmode; if (force_off) newmode = 0; else newmode = sc->pps_state.ppsparam.mode & PPS_CAPTUREASSERT; if (newmode == sc->pps_curmode) return; sc->pps_curmode = newmode; if (newmode == PPS_CAPTUREASSERT) sc->tclr |= DMT_TCLR_CAPTRAN_LOHI; else sc->tclr &= ~DMT_TCLR_CAPTRAN_MASK; DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr); } static unsigned dmtpps_get_timecount(struct timecounter *tc) { struct dmtpps_softc *sc; sc = tc->tc_priv; return (DMTIMER_READ4(sc, DMT_TCRR)); } static void dmtpps_poll(struct timecounter *tc) { struct dmtpps_softc *sc; sc = tc->tc_priv; /* * If a new value has been latched we've got a PPS event. Capture the * timecounter data, then override the capcount field (pps_capture() * populates it from the current DMT_TCRR register) with the latched * value from the TCAR1 register. * * Note that we don't have the TCAR interrupt enabled, but the hardware * still provides the status bits in the "RAW" status register even when * they're masked from generating an irq. However, when clearing the * TCAR status to re-arm the capture for the next second, we have to * write to the IRQ status register, not the RAW register. Quirky. * * We do not need to hold a lock while capturing the pps data, because * it is captured into an area of the pps_state struct which is read * only by pps_event(). We do need to hold a lock while calling * pps_event(), because it manipulates data which is also accessed from * the ioctl(2) context by userland processes. */ if (DMTIMER_READ4(sc, DMT_IRQSTATUS_RAW) & DMT_IRQ_TCAR) { pps_capture(&sc->pps_state); sc->pps_state.capcount = DMTIMER_READ4(sc, DMT_TCAR1); DMTIMER_WRITE4(sc, DMT_IRQSTATUS, DMT_IRQ_TCAR); mtx_lock_spin(&sc->pps_mtx); pps_event(&sc->pps_state, PPS_CAPTUREASSERT); mtx_unlock_spin(&sc->pps_mtx); } } static int dmtpps_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct dmtpps_softc *sc; sc = dev->si_drv1; /* * Begin polling for pps and enable capture in the hardware whenever the * device is open. Doing this stuff again is harmless if this isn't the * first open. */ sc->tc.tc_poll_pps = dmtpps_poll; dmtpps_set_hw_capture(sc, false); return 0; } static int dmtpps_close(struct cdev *dev, int flags, int fmt, struct thread *td) { struct dmtpps_softc *sc; sc = dev->si_drv1; /* * Stop polling and disable capture on last close. Use the force-off * flag to override the configured mode and turn off the hardware. */ sc->tc.tc_poll_pps = NULL; dmtpps_set_hw_capture(sc, true); return 0; } static int dmtpps_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { struct dmtpps_softc *sc; int err; sc = dev->si_drv1; /* Let the kernel do the heavy lifting for ioctl. */ mtx_lock_spin(&sc->pps_mtx); err = pps_ioctl(cmd, data, &sc->pps_state); mtx_unlock_spin(&sc->pps_mtx); if (err != 0) return (err); /* * The capture mode could have changed, set the hardware to whatever * mode is now current. Effectively a no-op if nothing changed. */ dmtpps_set_hw_capture(sc, false); return (err); } static struct cdevsw dmtpps_cdevsw = { .d_version = D_VERSION, .d_open = dmtpps_open, .d_close = dmtpps_close, .d_ioctl = dmtpps_ioctl, .d_name = PPS_CDEV_NAME, }; static int dmtpps_probe(device_t dev) { char strbuf[64]; int tmr_num; uint64_t rev_address; if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); /* * If we haven't chosen which hardware timer to use yet, go do that now. * We need to know that to decide whether to return success for this * hardware timer instance or not. */ if (dmtpps_tmr_num == 0) dmtpps_tmr_num = dmtpps_find_tmr_num(); /* * Figure out which hardware timer is being probed and see if it matches * the configured timer number determined earlier. */ rev_address = ti_sysc_get_rev_address(device_get_parent(dev)); switch (rev_address) { case DMTIMER1_1MS_REV: tmr_num = 1; break; case DMTIMER2_REV: tmr_num = 2; break; case DMTIMER3_REV: tmr_num = 3; break; case DMTIMER4_REV: tmr_num = 4; break; case DMTIMER5_REV: tmr_num = 5; break; case DMTIMER6_REV: tmr_num = 6; break; case DMTIMER7_REV: tmr_num = 7; break; default: return (ENXIO); } if (dmtpps_tmr_num != tmr_num) return (ENXIO); snprintf(strbuf, sizeof(strbuf), "AM335x PPS-Capture DMTimer%d", tmr_num); device_set_desc_copy(dev, strbuf); return(BUS_PROBE_DEFAULT); } static int dmtpps_attach(device_t dev) { struct dmtpps_softc *sc; struct make_dev_args mda; int err; clk_t sys_clkin; uint64_t rev_address; sc = device_get_softc(dev); sc->dev = dev; /* Figure out which hardware timer this is and set the name string. */ rev_address = ti_sysc_get_rev_address(device_get_parent(dev)); switch (rev_address) { case DMTIMER1_1MS_REV: sc->tmr_num = 1; break; case DMTIMER2_REV: sc->tmr_num = 2; break; case DMTIMER3_REV: sc->tmr_num = 3; break; case DMTIMER4_REV: sc->tmr_num = 4; break; case DMTIMER5_REV: sc->tmr_num = 5; break; case DMTIMER6_REV: sc->tmr_num = 6; break; case DMTIMER7_REV: sc->tmr_num = 7; break; } snprintf(sc->tmr_name, sizeof(sc->tmr_name), "DMTimer%d", sc->tmr_num); /* expect one clock */ err = clk_get_by_ofw_index(dev, 0, 0, &sc->clk_fck); if (err != 0) { device_printf(dev, "Cant find clock index 0. err: %d\n", err); return (ENXIO); } err = clk_get_by_name(dev, "sys_clkin_ck@40", &sys_clkin); if (err != 0) { device_printf(dev, "Cant find sys_clkin_ck@40 err: %d\n", err); return (ENXIO); } /* Select M_OSC as DPLL parent */ err = clk_set_parent_by_clk(sc->clk_fck, sys_clkin); if (err != 0) { device_printf(dev, "Cant set mux to CLK_M_OSC\n"); return (ENXIO); } /* Enable clocks and power on the device. */ err = ti_sysc_clock_enable(device_get_parent(dev)); if (err != 0) { device_printf(dev, "Cant enable sysc clkctrl, err %d\n", err); return (ENXIO); } /* Get the base clock frequency. */ err = clk_get_freq(sc->clk_fck, &sc->sysclk_freq); if (err != 0) { device_printf(dev, "Cant get sysclk frequency, err %d\n", err); return (ENXIO); } /* Request the memory resources. */ sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, RF_ACTIVE); if (sc->mem_res == NULL) { return (ENXIO); } /* * Configure the timer pulse/capture pin to input/capture mode. This is * required in addition to configuring the pin as input with the pinmux * controller (which was done via fdt data or tunable at probe time). */ sc->tclr = DMT_TCLR_GPO_CFG; DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr); /* Set up timecounter hardware, start it. */ DMTIMER_WRITE4(sc, DMT_TSICR, DMT_TSICR_RESET); while (DMTIMER_READ4(sc, DMT_TIOCP_CFG) & DMT_TIOCP_RESET) continue; sc->tclr |= DMT_TCLR_START | DMT_TCLR_AUTOLOAD; DMTIMER_WRITE4(sc, DMT_TLDR, 0); DMTIMER_WRITE4(sc, DMT_TCRR, 0); DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr); /* Register the timecounter. */ sc->tc.tc_name = sc->tmr_name; sc->tc.tc_get_timecount = dmtpps_get_timecount; sc->tc.tc_counter_mask = ~0u; sc->tc.tc_frequency = sc->sysclk_freq; sc->tc.tc_quality = 1000; sc->tc.tc_priv = sc; tc_init(&sc->tc); /* * Indicate our PPS capabilities. Have the kernel init its part of the * pps_state struct and add its capabilities. * * While the hardware has a mode to capture each edge, it's not clear we * can use it that way, because there's only a single interrupt/status * bit to say something was captured, but not which edge it was. For * now, just say we can only capture assert events (the positive-going * edge of the pulse). */ mtx_init(&sc->pps_mtx, "dmtpps", NULL, MTX_SPIN); sc->pps_state.flags = PPSFLAG_MTX_SPIN; sc->pps_state.ppscap = PPS_CAPTUREASSERT; sc->pps_state.driver_abi = PPS_ABI_VERSION; sc->pps_state.driver_mtx = &sc->pps_mtx; pps_init_abi(&sc->pps_state); /* Create the PPS cdev. */ make_dev_args_init(&mda); mda.mda_flags = MAKEDEV_WAITOK; mda.mda_devsw = &dmtpps_cdevsw; mda.mda_cr = NULL; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_WHEEL; mda.mda_mode = 0600; mda.mda_unit = device_get_unit(dev); mda.mda_si_drv1 = sc; if ((err = make_dev_s(&mda, &sc->pps_cdev, PPS_CDEV_NAME)) != 0) { device_printf(dev, "Failed to create cdev %s\n", PPS_CDEV_NAME); return (err); } if (bootverbose) device_printf(sc->dev, "Using %s for PPS device /dev/%s\n", sc->tmr_name, PPS_CDEV_NAME); return (0); } static int dmtpps_detach(device_t dev) { /* * There is no way to remove a timecounter once it has been registered, * even if it's not in use, so we can never detach. If we were * dynamically loaded as a module this will prevent unloading. */ return (EBUSY); } static device_method_t dmtpps_methods[] = { DEVMETHOD(device_probe, dmtpps_probe), DEVMETHOD(device_attach, dmtpps_attach), DEVMETHOD(device_detach, dmtpps_detach), { 0, 0 } }; static driver_t dmtpps_driver = { "am335x_dmtpps", dmtpps_methods, sizeof(struct dmtpps_softc), }; static devclass_t dmtpps_devclass; DRIVER_MODULE(am335x_dmtpps, simplebus, dmtpps_driver, dmtpps_devclass, 0, 0); -SIMPLEBUS_PNP_INFO(compat_data); MODULE_DEPEND(am335x_dmtpps, ti_sysc, 1, 1, 1); diff --git a/sys/arm/ti/am335x/am335x_ehrpwm.c b/sys/arm/ti/am335x/am335x_ehrpwm.c index 6fd709d120e6..619fdae2bcdb 100644 --- a/sys/arm/ti/am335x/am335x_ehrpwm.c +++ b/sys/arm/ti/am335x/am335x_ehrpwm.c @@ -1,718 +1,718 @@ /*- * Copyright (c) 2013 Oleksandr Tymoshenko * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pwmbus_if.h" #include "am335x_pwm.h" /******************************************************************************* * Enhanced resolution PWM driver. Many of the advanced featues of the hardware * are not supported by this driver. What is implemented here is simple * variable-duty-cycle PWM output. * * Note that this driver was historically configured using a set of sysctl * variables/procs, and later gained support for the PWM(9) API. The sysctl * code is still present to support existing apps, but that interface is * considered deprecated. * * An important caveat is that the original sysctl interface and the new PWM API * cannot both be used at once. If both interfaces are used to change * configuration, it's quite likely you won't get the expected results. Also, * reading the sysctl values after configuring via PWM will not return the right * results. ******************************************************************************/ /* In ticks */ #define DEFAULT_PWM_PERIOD 1000 #define PWM_CLOCK 100000000UL #define NS_PER_SEC 1000000000 #define PWM_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define PWM_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define PWM_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define PWM_LOCK_INIT(_sc) mtx_init(&(_sc)->sc_mtx, \ device_get_nameunit(_sc->sc_dev), "am335x_ehrpwm softc", MTX_DEF) #define PWM_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) #define EPWM_READ2(_sc, reg) bus_read_2((_sc)->sc_mem_res, reg) #define EPWM_WRITE2(_sc, reg, value) \ bus_write_2((_sc)->sc_mem_res, reg, value) #define EPWM_TBCTL 0x00 #define TBCTL_FREERUN (2 << 14) #define TBCTL_PHDIR_UP (1 << 13) #define TBCTL_PHDIR_DOWN (0 << 13) #define TBCTL_CLKDIV(x) ((x) << 10) #define TBCTL_CLKDIV_MASK (3 << 10) #define TBCTL_HSPCLKDIV(x) ((x) << 7) #define TBCTL_HSPCLKDIV_MASK (3 << 7) #define TBCTL_SYNCOSEL_DISABLED (3 << 4) #define TBCTL_PRDLD_SHADOW (0 << 3) #define TBCTL_PRDLD_IMMEDIATE (0 << 3) #define TBCTL_PHSEN_ENABLED (1 << 2) #define TBCTL_PHSEN_DISABLED (0 << 2) #define TBCTL_CTRMODE_MASK (3) #define TBCTL_CTRMODE_UP (0 << 0) #define TBCTL_CTRMODE_DOWN (1 << 0) #define TBCTL_CTRMODE_UPDOWN (2 << 0) #define TBCTL_CTRMODE_FREEZE (3 << 0) #define EPWM_TBSTS 0x02 #define EPWM_TBPHSHR 0x04 #define EPWM_TBPHS 0x06 #define EPWM_TBCNT 0x08 #define EPWM_TBPRD 0x0a /* Counter-compare */ #define EPWM_CMPCTL 0x0e #define CMPCTL_SHDWBMODE_SHADOW (1 << 6) #define CMPCTL_SHDWBMODE_IMMEDIATE (0 << 6) #define CMPCTL_SHDWAMODE_SHADOW (1 << 4) #define CMPCTL_SHDWAMODE_IMMEDIATE (0 << 4) #define CMPCTL_LOADBMODE_ZERO (0 << 2) #define CMPCTL_LOADBMODE_PRD (1 << 2) #define CMPCTL_LOADBMODE_EITHER (2 << 2) #define CMPCTL_LOADBMODE_FREEZE (3 << 2) #define CMPCTL_LOADAMODE_ZERO (0 << 0) #define CMPCTL_LOADAMODE_PRD (1 << 0) #define CMPCTL_LOADAMODE_EITHER (2 << 0) #define CMPCTL_LOADAMODE_FREEZE (3 << 0) #define EPWM_CMPAHR 0x10 #define EPWM_CMPA 0x12 #define EPWM_CMPB 0x14 /* CMPCTL_LOADAMODE_ZERO */ #define EPWM_AQCTLA 0x16 #define EPWM_AQCTLB 0x18 #define AQCTL_CBU_NONE (0 << 8) #define AQCTL_CBU_CLEAR (1 << 8) #define AQCTL_CBU_SET (2 << 8) #define AQCTL_CBU_TOGGLE (3 << 8) #define AQCTL_CAU_NONE (0 << 4) #define AQCTL_CAU_CLEAR (1 << 4) #define AQCTL_CAU_SET (2 << 4) #define AQCTL_CAU_TOGGLE (3 << 4) #define AQCTL_ZRO_NONE (0 << 0) #define AQCTL_ZRO_CLEAR (1 << 0) #define AQCTL_ZRO_SET (2 << 0) #define AQCTL_ZRO_TOGGLE (3 << 0) #define EPWM_AQSFRC 0x1a #define EPWM_AQCSFRC 0x1c #define AQCSFRC_OFF 0 #define AQCSFRC_LO 1 #define AQCSFRC_HI 2 #define AQCSFRC_MASK 3 #define AQCSFRC(chan, hilo) ((hilo) << (2 * chan)) /* Trip-Zone module */ #define EPWM_TZCTL 0x28 #define EPWM_TZFLG 0x2C /* High-Resolution PWM */ #define EPWM_HRCTL 0x40 #define HRCTL_DELMODE_BOTH 3 #define HRCTL_DELMODE_FALL 2 #define HRCTL_DELMODE_RISE 1 static device_probe_t am335x_ehrpwm_probe; static device_attach_t am335x_ehrpwm_attach; static device_detach_t am335x_ehrpwm_detach; static int am335x_ehrpwm_clkdiv[8] = { 1, 2, 4, 8, 16, 32, 64, 128 }; struct ehrpwm_channel { u_int duty; /* on duration, in ns */ bool enabled; /* channel enabled? */ bool inverted; /* signal inverted? */ }; #define NUM_CHANNELS 2 struct am335x_ehrpwm_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource *sc_mem_res; int sc_mem_rid; /* Things used for configuration via sysctl [deprecated]. */ int sc_pwm_clkdiv; int sc_pwm_freq; struct sysctl_oid *sc_clkdiv_oid; struct sysctl_oid *sc_freq_oid; struct sysctl_oid *sc_period_oid; struct sysctl_oid *sc_chanA_oid; struct sysctl_oid *sc_chanB_oid; uint32_t sc_pwm_period; uint32_t sc_pwm_dutyA; uint32_t sc_pwm_dutyB; /* Things used for configuration via pwm(9) api. */ u_int sc_clkfreq; /* frequency in Hz */ u_int sc_clktick; /* duration in ns */ u_int sc_period; /* duration in ns */ struct ehrpwm_channel sc_channels[NUM_CHANNELS]; }; static struct ofw_compat_data compat_data[] = { {"ti,am33xx-ehrpwm", true}, {NULL, false}, }; +SIMPLEBUS_PNP_INFO(compat_data); static void am335x_ehrpwm_cfg_duty(struct am335x_ehrpwm_softc *sc, u_int chan, u_int duty) { u_int tbcmp; if (duty == 0) tbcmp = 0; else tbcmp = max(1, duty / sc->sc_clktick); sc->sc_channels[chan].duty = tbcmp * sc->sc_clktick; PWM_LOCK_ASSERT(sc); EPWM_WRITE2(sc, (chan == 0) ? EPWM_CMPA : EPWM_CMPB, tbcmp); } static void am335x_ehrpwm_cfg_enable(struct am335x_ehrpwm_softc *sc, u_int chan, bool enable) { uint16_t regval; sc->sc_channels[chan].enabled = enable; /* * Turn off any existing software-force of the channel, then force * it in the right direction (high or low) if it's not being enabled. */ PWM_LOCK_ASSERT(sc); regval = EPWM_READ2(sc, EPWM_AQCSFRC); regval &= ~AQCSFRC(chan, AQCSFRC_MASK); if (!sc->sc_channels[chan].enabled) { if (sc->sc_channels[chan].inverted) regval |= AQCSFRC(chan, AQCSFRC_HI); else regval |= AQCSFRC(chan, AQCSFRC_LO); } EPWM_WRITE2(sc, EPWM_AQCSFRC, regval); } static bool am335x_ehrpwm_cfg_period(struct am335x_ehrpwm_softc *sc, u_int period) { uint16_t regval; u_int clkdiv, hspclkdiv, pwmclk, pwmtick, tbprd; /* Can't do a period shorter than 2 clock ticks. */ if (period < 2 * NS_PER_SEC / PWM_CLOCK) { sc->sc_clkfreq = 0; sc->sc_clktick = 0; sc->sc_period = 0; return (false); } /* * Figure out how much we have to divide down the base 100MHz clock so * that we can express the requested period as a 16-bit tick count. */ tbprd = 0; for (clkdiv = 0; clkdiv < 8; ++clkdiv) { const u_int cd = 1 << clkdiv; for (hspclkdiv = 0; hspclkdiv < 8; ++hspclkdiv) { const u_int cdhs = max(1, hspclkdiv * 2); pwmclk = PWM_CLOCK / (cd * cdhs); pwmtick = NS_PER_SEC / pwmclk; if (period / pwmtick < 65536) { tbprd = period / pwmtick; break; } } if (tbprd != 0) break; } /* Handle requested period too long for available clock divisors. */ if (tbprd == 0) return (false); /* * If anything has changed from the current settings, reprogram the * clock divisors and period register. */ if (sc->sc_clkfreq != pwmclk || sc->sc_clktick != pwmtick || sc->sc_period != tbprd * pwmtick) { sc->sc_clkfreq = pwmclk; sc->sc_clktick = pwmtick; sc->sc_period = tbprd * pwmtick; PWM_LOCK_ASSERT(sc); regval = EPWM_READ2(sc, EPWM_TBCTL); regval &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK); regval |= TBCTL_CLKDIV(clkdiv) | TBCTL_HSPCLKDIV(hspclkdiv); EPWM_WRITE2(sc, EPWM_TBCTL, regval); EPWM_WRITE2(sc, EPWM_TBPRD, tbprd - 1); #if 0 device_printf(sc->sc_dev, "clkdiv %u hspclkdiv %u tbprd %u " "clkfreq %u Hz clktick %u ns period got %u requested %u\n", clkdiv, hspclkdiv, tbprd - 1, sc->sc_clkfreq, sc->sc_clktick, sc->sc_period, period); #endif /* * If the period changed, that invalidates the current CMP * registers (duty values), just zero them out. */ am335x_ehrpwm_cfg_duty(sc, 0, 0); am335x_ehrpwm_cfg_duty(sc, 1, 0); } return (true); } static void am335x_ehrpwm_freq(struct am335x_ehrpwm_softc *sc) { int clkdiv; clkdiv = am335x_ehrpwm_clkdiv[sc->sc_pwm_clkdiv]; sc->sc_pwm_freq = PWM_CLOCK / (1 * clkdiv) / sc->sc_pwm_period; } static int am335x_ehrpwm_sysctl_freq(SYSCTL_HANDLER_ARGS) { int clkdiv, error, freq, i, period; struct am335x_ehrpwm_softc *sc; uint32_t reg; sc = (struct am335x_ehrpwm_softc *)arg1; PWM_LOCK(sc); freq = sc->sc_pwm_freq; PWM_UNLOCK(sc); error = sysctl_handle_int(oidp, &freq, sizeof(freq), req); if (error != 0 || req->newptr == NULL) return (error); if (freq > PWM_CLOCK) freq = PWM_CLOCK; PWM_LOCK(sc); if (freq != sc->sc_pwm_freq) { for (i = nitems(am335x_ehrpwm_clkdiv) - 1; i >= 0; i--) { clkdiv = am335x_ehrpwm_clkdiv[i]; period = PWM_CLOCK / clkdiv / freq; if (period > USHRT_MAX) break; sc->sc_pwm_clkdiv = i; sc->sc_pwm_period = period; } /* Reset the duty cycle settings. */ sc->sc_pwm_dutyA = 0; sc->sc_pwm_dutyB = 0; EPWM_WRITE2(sc, EPWM_CMPA, sc->sc_pwm_dutyA); EPWM_WRITE2(sc, EPWM_CMPB, sc->sc_pwm_dutyB); /* Update the clkdiv settings. */ reg = EPWM_READ2(sc, EPWM_TBCTL); reg &= ~TBCTL_CLKDIV_MASK; reg |= TBCTL_CLKDIV(sc->sc_pwm_clkdiv); EPWM_WRITE2(sc, EPWM_TBCTL, reg); /* Update the period settings. */ EPWM_WRITE2(sc, EPWM_TBPRD, sc->sc_pwm_period - 1); am335x_ehrpwm_freq(sc); } PWM_UNLOCK(sc); return (0); } static int am335x_ehrpwm_sysctl_clkdiv(SYSCTL_HANDLER_ARGS) { int error, i, clkdiv; struct am335x_ehrpwm_softc *sc; uint32_t reg; sc = (struct am335x_ehrpwm_softc *)arg1; PWM_LOCK(sc); clkdiv = am335x_ehrpwm_clkdiv[sc->sc_pwm_clkdiv]; PWM_UNLOCK(sc); error = sysctl_handle_int(oidp, &clkdiv, sizeof(clkdiv), req); if (error != 0 || req->newptr == NULL) return (error); PWM_LOCK(sc); if (clkdiv != am335x_ehrpwm_clkdiv[sc->sc_pwm_clkdiv]) { for (i = 0; i < nitems(am335x_ehrpwm_clkdiv); i++) if (clkdiv >= am335x_ehrpwm_clkdiv[i]) sc->sc_pwm_clkdiv = i; reg = EPWM_READ2(sc, EPWM_TBCTL); reg &= ~TBCTL_CLKDIV_MASK; reg |= TBCTL_CLKDIV(sc->sc_pwm_clkdiv); EPWM_WRITE2(sc, EPWM_TBCTL, reg); am335x_ehrpwm_freq(sc); } PWM_UNLOCK(sc); return (0); } static int am335x_ehrpwm_sysctl_duty(SYSCTL_HANDLER_ARGS) { struct am335x_ehrpwm_softc *sc = (struct am335x_ehrpwm_softc*)arg1; int error; uint32_t duty; if (oidp == sc->sc_chanA_oid) duty = sc->sc_pwm_dutyA; else duty = sc->sc_pwm_dutyB; error = sysctl_handle_int(oidp, &duty, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (duty > sc->sc_pwm_period) { device_printf(sc->sc_dev, "Duty cycle can't be greater then period\n"); return (EINVAL); } PWM_LOCK(sc); if (oidp == sc->sc_chanA_oid) { sc->sc_pwm_dutyA = duty; EPWM_WRITE2(sc, EPWM_CMPA, sc->sc_pwm_dutyA); } else { sc->sc_pwm_dutyB = duty; EPWM_WRITE2(sc, EPWM_CMPB, sc->sc_pwm_dutyB); } PWM_UNLOCK(sc); return (error); } static int am335x_ehrpwm_sysctl_period(SYSCTL_HANDLER_ARGS) { struct am335x_ehrpwm_softc *sc = (struct am335x_ehrpwm_softc*)arg1; int error; uint32_t period; period = sc->sc_pwm_period; error = sysctl_handle_int(oidp, &period, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (period < 1) return (EINVAL); if (period > USHRT_MAX) period = USHRT_MAX; PWM_LOCK(sc); /* Reset the duty cycle settings. */ sc->sc_pwm_dutyA = 0; sc->sc_pwm_dutyB = 0; EPWM_WRITE2(sc, EPWM_CMPA, sc->sc_pwm_dutyA); EPWM_WRITE2(sc, EPWM_CMPB, sc->sc_pwm_dutyB); /* Update the period settings. */ sc->sc_pwm_period = period; EPWM_WRITE2(sc, EPWM_TBPRD, period - 1); am335x_ehrpwm_freq(sc); PWM_UNLOCK(sc); return (error); } static int am335x_ehrpwm_channel_count(device_t dev, u_int *nchannel) { *nchannel = NUM_CHANNELS; return (0); } static int am335x_ehrpwm_channel_config(device_t dev, u_int channel, u_int period, u_int duty) { struct am335x_ehrpwm_softc *sc; bool status; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); PWM_LOCK(sc); status = am335x_ehrpwm_cfg_period(sc, period); if (status) am335x_ehrpwm_cfg_duty(sc, channel, duty); PWM_UNLOCK(sc); return (status ? 0 : EINVAL); } static int am335x_ehrpwm_channel_get_config(device_t dev, u_int channel, u_int *period, u_int *duty) { struct am335x_ehrpwm_softc *sc; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); *period = sc->sc_period; *duty = sc->sc_channels[channel].duty; return (0); } static int am335x_ehrpwm_channel_enable(device_t dev, u_int channel, bool enable) { struct am335x_ehrpwm_softc *sc; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); PWM_LOCK(sc); am335x_ehrpwm_cfg_enable(sc, channel, enable); PWM_UNLOCK(sc); return (0); } static int am335x_ehrpwm_channel_is_enabled(device_t dev, u_int channel, bool *enabled) { struct am335x_ehrpwm_softc *sc; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); *enabled = sc->sc_channels[channel].enabled; return (0); } static int am335x_ehrpwm_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "AM335x EHRPWM"); return (BUS_PROBE_DEFAULT); } static int am335x_ehrpwm_attach(device_t dev) { struct am335x_ehrpwm_softc *sc; uint32_t reg; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; sc = device_get_softc(dev); sc->sc_dev = dev; PWM_LOCK_INIT(sc); sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "cannot allocate memory resources\n"); goto fail; } /* Init sysctl interface */ ctx = device_get_sysctl_ctx(sc->sc_dev); tree = device_get_sysctl_tree(sc->sc_dev); sc->sc_clkdiv_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "clkdiv", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, am335x_ehrpwm_sysctl_clkdiv, "I", "PWM clock prescaler"); sc->sc_freq_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, am335x_ehrpwm_sysctl_freq, "I", "PWM frequency"); sc->sc_period_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "period", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, am335x_ehrpwm_sysctl_period, "I", "PWM period"); sc->sc_chanA_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "dutyA", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, am335x_ehrpwm_sysctl_duty, "I", "Channel A duty cycles"); sc->sc_chanB_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "dutyB", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, am335x_ehrpwm_sysctl_duty, "I", "Channel B duty cycles"); /* CONFIGURE EPWM1 */ reg = EPWM_READ2(sc, EPWM_TBCTL); reg &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK); EPWM_WRITE2(sc, EPWM_TBCTL, reg); sc->sc_pwm_period = DEFAULT_PWM_PERIOD; sc->sc_pwm_dutyA = 0; sc->sc_pwm_dutyB = 0; am335x_ehrpwm_freq(sc); EPWM_WRITE2(sc, EPWM_TBPRD, sc->sc_pwm_period - 1); EPWM_WRITE2(sc, EPWM_CMPA, sc->sc_pwm_dutyA); EPWM_WRITE2(sc, EPWM_CMPB, sc->sc_pwm_dutyB); EPWM_WRITE2(sc, EPWM_AQCTLA, (AQCTL_ZRO_SET | AQCTL_CAU_CLEAR)); EPWM_WRITE2(sc, EPWM_AQCTLB, (AQCTL_ZRO_SET | AQCTL_CBU_CLEAR)); /* START EPWM */ reg &= ~TBCTL_CTRMODE_MASK; reg |= TBCTL_CTRMODE_UP | TBCTL_FREERUN; EPWM_WRITE2(sc, EPWM_TBCTL, reg); EPWM_WRITE2(sc, EPWM_TZCTL, 0xf); reg = EPWM_READ2(sc, EPWM_TZFLG); if ((sc->sc_busdev = device_add_child(dev, "pwmbus", -1)) == NULL) { device_printf(dev, "Cannot add child pwmbus\n"); // This driver can still do things even without the bus child. } bus_generic_probe(dev); return (bus_generic_attach(dev)); fail: PWM_LOCK_DESTROY(sc); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); return(ENXIO); } static int am335x_ehrpwm_detach(device_t dev) { struct am335x_ehrpwm_softc *sc; int error; sc = device_get_softc(dev); if ((error = bus_generic_detach(sc->sc_dev)) != 0) return (error); PWM_LOCK(sc); if (sc->sc_busdev != NULL) device_delete_child(dev, sc->sc_busdev); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); PWM_UNLOCK(sc); PWM_LOCK_DESTROY(sc); return (0); } static phandle_t am335x_ehrpwm_get_node(device_t bus, device_t dev) { /* * Share our controller node with our pwmbus child; it instantiates * devices by walking the children contained within our node. */ return ofw_bus_get_node(bus); } static device_method_t am335x_ehrpwm_methods[] = { DEVMETHOD(device_probe, am335x_ehrpwm_probe), DEVMETHOD(device_attach, am335x_ehrpwm_attach), DEVMETHOD(device_detach, am335x_ehrpwm_detach), /* ofw_bus_if */ DEVMETHOD(ofw_bus_get_node, am335x_ehrpwm_get_node), /* pwm interface */ DEVMETHOD(pwmbus_channel_count, am335x_ehrpwm_channel_count), DEVMETHOD(pwmbus_channel_config, am335x_ehrpwm_channel_config), DEVMETHOD(pwmbus_channel_get_config, am335x_ehrpwm_channel_get_config), DEVMETHOD(pwmbus_channel_enable, am335x_ehrpwm_channel_enable), DEVMETHOD(pwmbus_channel_is_enabled, am335x_ehrpwm_channel_is_enabled), DEVMETHOD_END }; static driver_t am335x_ehrpwm_driver = { "pwm", am335x_ehrpwm_methods, sizeof(struct am335x_ehrpwm_softc), }; static devclass_t am335x_ehrpwm_devclass; DRIVER_MODULE(am335x_ehrpwm, am335x_pwmss, am335x_ehrpwm_driver, am335x_ehrpwm_devclass, 0, 0); -SIMPLEBUS_PNP_INFO(compat_data); MODULE_VERSION(am335x_ehrpwm, 1); MODULE_DEPEND(am335x_ehrpwm, am335x_pwmss, 1, 1, 1); MODULE_DEPEND(am335x_ehrpwm, pwmbus, 1, 1, 1); diff --git a/sys/dev/gpio/gpioiic.c b/sys/dev/gpio/gpioiic.c index f387252098e2..6d62a6d04d95 100644 --- a/sys/dev/gpio/gpioiic.c +++ b/sys/dev/gpio/gpioiic.c @@ -1,380 +1,376 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 Oleksandr Tymoshenko * Copyright (c) 2010 Luiz Otavio O Souza * Copyright (c) 2019 Ian Lepore * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include "gpiobus_if.h" #include "iicbb_if.h" #define GPIOIIC_SCL_DFLT 0 #define GPIOIIC_SDA_DFLT 1 #define GPIOIIC_MIN_PINS 2 struct gpioiic_softc { device_t dev; gpio_pin_t sclpin; gpio_pin_t sdapin; }; #ifdef FDT #include static struct ofw_compat_data compat_data[] = { {"i2c-gpio", true}, /* Standard devicetree compat string */ {"gpioiic", true}, /* Deprecated old freebsd compat string */ {NULL, false} }; +OFWBUS_PNP_INFO(compat_data); +SIMPLEBUS_PNP_INFO(compat_data); static phandle_t gpioiic_get_node(device_t bus, device_t dev) { /* Share our fdt node with iicbus so it can find its child nodes. */ return (ofw_bus_get_node(bus)); } static int gpioiic_setup_fdt_pins(struct gpioiic_softc *sc) { phandle_t node; int err; node = ofw_bus_get_node(sc->dev); /* * Historically, we used the first two array elements of the gpios * property. The modern bindings specify separate scl-gpios and * sda-gpios properties. We cope with whichever is present. */ if (OF_hasprop(node, "gpios")) { if ((err = gpio_pin_get_by_ofw_idx(sc->dev, node, GPIOIIC_SCL_DFLT, &sc->sclpin)) != 0) { device_printf(sc->dev, "invalid gpios property\n"); return (err); } if ((err = gpio_pin_get_by_ofw_idx(sc->dev, node, GPIOIIC_SDA_DFLT, &sc->sdapin)) != 0) { device_printf(sc->dev, "ivalid gpios property\n"); return (err); } } else { if ((err = gpio_pin_get_by_ofw_property(sc->dev, node, "scl-gpios", &sc->sclpin)) != 0) { device_printf(sc->dev, "missing scl-gpios property\n"); return (err); } if ((err = gpio_pin_get_by_ofw_property(sc->dev, node, "sda-gpios", &sc->sdapin)) != 0) { device_printf(sc->dev, "missing sda-gpios property\n"); return (err); } } return (0); } #endif /* FDT */ static int gpioiic_setup_hinted_pins(struct gpioiic_softc *sc) { device_t busdev; const char *busname, *devname; int err, numpins, sclnum, sdanum, unit; devname = device_get_name(sc->dev); unit = device_get_unit(sc->dev); busdev = device_get_parent(sc->dev); /* * If there is not an "at" hint naming our actual parent, then we * weren't instantiated as a child of gpiobus via hints, and we thus * can't access ivars that only exist for such children. */ if (resource_string_value(devname, unit, "at", &busname) != 0 || (strcmp(busname, device_get_nameunit(busdev)) != 0 && strcmp(busname, device_get_name(busdev)) != 0)) { return (ENOENT); } /* Make sure there were hints for at least two pins. */ numpins = gpiobus_get_npins(sc->dev); if (numpins < GPIOIIC_MIN_PINS) { #ifdef FDT /* * Be silent when there are no hints on FDT systems; the FDT * data will provide the pin config (we'll whine if it doesn't). */ if (numpins == 0) { return (ENOENT); } #endif device_printf(sc->dev, "invalid pins hint; it must contain at least %d pins\n", GPIOIIC_MIN_PINS); return (EINVAL); } /* * Our parent bus has already parsed the pins hint and it will use that * info when we call gpio_pin_get_by_child_index(). But we have to * handle the scl/sda index hints that tell us which of the two pins is * the clock and which is the data. They're optional, but if present * they must be a valid index (0 <= index < numpins). */ if ((err = resource_int_value(devname, unit, "scl", &sclnum)) != 0) sclnum = GPIOIIC_SCL_DFLT; else if (sclnum < 0 || sclnum >= numpins) { device_printf(sc->dev, "invalid scl hint %d\n", sclnum); return (EINVAL); } if ((err = resource_int_value(devname, unit, "sda", &sdanum)) != 0) sdanum = GPIOIIC_SDA_DFLT; else if (sdanum < 0 || sdanum >= numpins) { device_printf(sc->dev, "invalid sda hint %d\n", sdanum); return (EINVAL); } /* Allocate gpiobus_pin structs for the pins we found above. */ if ((err = gpio_pin_get_by_child_index(sc->dev, sclnum, &sc->sclpin)) != 0) return (err); if ((err = gpio_pin_get_by_child_index(sc->dev, sdanum, &sc->sdapin)) != 0) return (err); return (0); } static void gpioiic_setsda(device_t dev, int val) { struct gpioiic_softc *sc = device_get_softc(dev); if (val) { gpio_pin_setflags(sc->sdapin, GPIO_PIN_INPUT); } else { gpio_pin_setflags(sc->sdapin, GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN); gpio_pin_set_active(sc->sdapin, 0); } } static void gpioiic_setscl(device_t dev, int val) { struct gpioiic_softc *sc = device_get_softc(dev); if (val) { gpio_pin_setflags(sc->sclpin, GPIO_PIN_INPUT); } else { gpio_pin_setflags(sc->sclpin, GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN); gpio_pin_set_active(sc->sclpin, 0); } } static int gpioiic_getscl(device_t dev) { struct gpioiic_softc *sc = device_get_softc(dev); bool val; gpio_pin_setflags(sc->sclpin, GPIO_PIN_INPUT); gpio_pin_is_active(sc->sclpin, &val); return (val); } static int gpioiic_getsda(device_t dev) { struct gpioiic_softc *sc = device_get_softc(dev); bool val; gpio_pin_setflags(sc->sdapin, GPIO_PIN_INPUT); gpio_pin_is_active(sc->sdapin, &val); return (val); } static int gpioiic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct gpioiic_softc *sc = device_get_softc(dev); /* Stop driving the bus pins. */ gpio_pin_setflags(sc->sdapin, GPIO_PIN_INPUT); gpio_pin_setflags(sc->sclpin, GPIO_PIN_INPUT); /* Indicate that we have no slave address (master mode). */ return (IIC_ENOADDR); } static void gpioiic_cleanup(struct gpioiic_softc *sc) { device_delete_children(sc->dev); if (sc->sclpin != NULL) gpio_pin_release(sc->sclpin); if (sc->sdapin != NULL) gpio_pin_release(sc->sdapin); } static int gpioiic_probe(device_t dev) { int rv; /* * By default we only bid to attach if specifically added by our parent * (usually via hint.gpioiic.#.at=busname). On FDT systems we bid as * the default driver based on being configured in the FDT data. */ rv = BUS_PROBE_NOWILDCARD; #ifdef FDT if (ofw_bus_status_okay(dev) && ofw_bus_search_compatible(dev, compat_data)->ocd_data) rv = BUS_PROBE_DEFAULT; #endif device_set_desc(dev, "GPIO I2C"); return (rv); } static int gpioiic_attach(device_t dev) { struct gpioiic_softc *sc = device_get_softc(dev); int err; sc->dev = dev; /* Acquire our gpio pins. */ err = gpioiic_setup_hinted_pins(sc); #ifdef FDT if (err != 0) err = gpioiic_setup_fdt_pins(sc); #endif if (err != 0) { device_printf(sc->dev, "no pins configured\n"); gpioiic_cleanup(sc); return (ENXIO); } /* * Say what we came up with for pin config. * NB: in the !FDT case the controller driver might not be set up enough * for GPIO_GET_BUS() to work. Also, our parent is the only gpiobus * that can provide our pins. */ device_printf(dev, "SCL pin: %s:%d, SDA pin: %s:%d\n", #ifdef FDT device_get_nameunit(GPIO_GET_BUS(sc->sclpin->dev)), sc->sclpin->pin, device_get_nameunit(GPIO_GET_BUS(sc->sdapin->dev)), sc->sdapin->pin); #else device_get_nameunit(device_get_parent(dev)), sc->sclpin->pin, device_get_nameunit(device_get_parent(dev)), sc->sdapin->pin); #endif /* Add the bitbang driver as our only child; it will add iicbus. */ device_add_child(sc->dev, "iicbb", -1); return (bus_generic_attach(dev)); } static int gpioiic_detach(device_t dev) { struct gpioiic_softc *sc = device_get_softc(dev); int err; if ((err = bus_generic_detach(dev)) != 0) return (err); gpioiic_cleanup(sc); return (0); } static devclass_t gpioiic_devclass; static device_method_t gpioiic_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gpioiic_probe), DEVMETHOD(device_attach, gpioiic_attach), DEVMETHOD(device_detach, gpioiic_detach), /* iicbb interface */ DEVMETHOD(iicbb_setsda, gpioiic_setsda), DEVMETHOD(iicbb_setscl, gpioiic_setscl), DEVMETHOD(iicbb_getsda, gpioiic_getsda), DEVMETHOD(iicbb_getscl, gpioiic_getscl), DEVMETHOD(iicbb_reset, gpioiic_reset), #ifdef FDT /* OFW bus interface */ DEVMETHOD(ofw_bus_get_node, gpioiic_get_node), #endif DEVMETHOD_END }; static driver_t gpioiic_driver = { "gpioiic", gpioiic_methods, sizeof(struct gpioiic_softc), }; DRIVER_MODULE(gpioiic, gpiobus, gpioiic_driver, gpioiic_devclass, 0, 0); -#ifdef FDT -OFWBUS_PNP_INFO(compat_data); -#endif DRIVER_MODULE(gpioiic, simplebus, gpioiic_driver, gpioiic_devclass, 0, 0); -#ifdef FDT -SIMPLEBUS_PNP_INFO(compat_data); -#endif DRIVER_MODULE(iicbb, gpioiic, iicbb_driver, iicbb_devclass, 0, 0); MODULE_DEPEND(gpioiic, iicbb, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER); MODULE_DEPEND(gpioiic, gpiobus, 1, 1, 1); diff --git a/sys/dev/gpio/gpiopps.c b/sys/dev/gpio/gpiopps.c index ea2644088c5d..8a6f1a6a3f6b 100644 --- a/sys/dev/gpio/gpiopps.c +++ b/sys/dev/gpio/gpiopps.c @@ -1,295 +1,295 @@ /*- * Copyright (c) 2016 Ian Lepore * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include "opt_platform.h" #ifdef FDT #include static struct ofw_compat_data compat_data[] = { {"pps-gpio", 1}, {NULL, 0} }; +SIMPLEBUS_PNP_INFO(compat_data); #endif /* FDT */ static devclass_t pps_devclass; struct pps_softc { device_t dev; gpio_pin_t gpin; void *ihandler; struct resource *ires; int irid; struct cdev *pps_cdev; struct pps_state pps_state; struct mtx pps_mtx; bool falling_edge; }; #define PPS_CDEV_NAME "gpiopps" static int gpiopps_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct pps_softc *sc = dev->si_drv1; /* We can't be unloaded while open, so mark ourselves BUSY. */ mtx_lock(&sc->pps_mtx); if (device_get_state(sc->dev) < DS_BUSY) { device_busy(sc->dev); } mtx_unlock(&sc->pps_mtx); return 0; } static int gpiopps_close(struct cdev *dev, int flags, int fmt, struct thread *td) { struct pps_softc *sc = dev->si_drv1; /* * Un-busy on last close. We rely on the vfs counting stuff to only call * this routine on last-close, so we don't need any open-count logic. */ mtx_lock(&sc->pps_mtx); device_unbusy(sc->dev); mtx_unlock(&sc->pps_mtx); return 0; } static int gpiopps_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { struct pps_softc *sc = dev->si_drv1; int err; /* Let the kernel do the heavy lifting for ioctl. */ mtx_lock(&sc->pps_mtx); err = pps_ioctl(cmd, data, &sc->pps_state); mtx_unlock(&sc->pps_mtx); return err; } static struct cdevsw pps_cdevsw = { .d_version = D_VERSION, .d_open = gpiopps_open, .d_close = gpiopps_close, .d_ioctl = gpiopps_ioctl, .d_name = PPS_CDEV_NAME, }; static int gpiopps_ifltr(void *arg) { struct pps_softc *sc = arg; /* * There is no locking here by design... The kernel cleverly captures * the current time into an area of the pps_state structure which is * written only by the pps_capture() routine and read only by the * pps_event() routine. We don't need lock-based management of access * to the capture area because we have time-based access management: we * can't be reading and writing concurently because we can't be running * both the threaded and filter handlers concurrently (because a new * hardware interrupt can't happen until the threaded handler for the * current interrupt exits, after which the system does the EOI that * enables a new hardware interrupt). */ pps_capture(&sc->pps_state); return (FILTER_SCHEDULE_THREAD); } static void gpiopps_ithrd(void *arg) { struct pps_softc *sc = arg; /* * Go create a pps event from the data captured in the filter handler. * * Note that we DO need locking here, unlike the case with the filter * handler. The pps_event() routine updates the non-capture part of the * pps_state structure, and the ioctl() code could be accessing that * data right now in a non-interrupt context, so we need an interlock. */ mtx_lock(&sc->pps_mtx); pps_event(&sc->pps_state, PPS_CAPTUREASSERT); mtx_unlock(&sc->pps_mtx); } static int gpiopps_detach(device_t dev) { struct pps_softc *sc = device_get_softc(dev); if (sc->pps_cdev != NULL) destroy_dev(sc->pps_cdev); if (sc->ihandler != NULL) bus_teardown_intr(dev, sc->ires, sc->ihandler); if (sc->ires != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->irid, sc->ires); if (sc->gpin != NULL) gpiobus_release_pin(GPIO_GET_BUS(sc->gpin->dev), sc->gpin->pin); return (0); } #ifdef FDT static int gpiopps_fdt_attach(device_t dev) { struct pps_softc *sc; struct make_dev_args devargs; phandle_t node; uint32_t edge, pincaps; int err; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->pps_mtx, device_get_nameunit(dev), NULL, MTX_DEF); /* Initialize the pps_state struct. */ sc->pps_state.ppscap = PPS_CAPTUREASSERT | PPS_CAPTURECLEAR; sc->pps_state.driver_abi = PPS_ABI_VERSION; sc->pps_state.driver_mtx = &sc->pps_mtx; pps_init_abi(&sc->pps_state); /* Check which edge we're configured to capture (default is rising). */ if (ofw_bus_has_prop(dev, "assert-falling-edge")) edge = GPIO_INTR_EDGE_FALLING; else edge = GPIO_INTR_EDGE_RISING; /* * Look up the configured gpio pin and ensure it can be configured for * the interrupt mode we need. */ node = ofw_bus_get_node(dev); if ((err = gpio_pin_get_by_ofw_idx(dev, node, 0, &sc->gpin)) != 0) { device_printf(dev, "Cannot obtain gpio pin\n"); return (err); } device_printf(dev, "PPS input on %s pin %u\n", device_get_nameunit(sc->gpin->dev), sc->gpin->pin); if ((err = gpio_pin_getcaps(sc->gpin, &pincaps)) != 0) { device_printf(dev, "Cannot query capabilities of gpio pin\n"); gpiopps_detach(dev); return (err); } if ((pincaps & edge) == 0) { device_printf(dev, "Pin cannot be configured for the requested signal edge\n"); gpiopps_detach(dev); return (ENOTSUP); } /* * Transform our 'gpios' property into an interrupt resource and set up * the interrupt. */ if ((sc->ires = gpio_alloc_intr_resource(dev, &sc->irid, RF_ACTIVE, sc->gpin, edge)) == NULL) { device_printf(dev, "Cannot allocate an IRQ for the GPIO\n"); gpiopps_detach(dev); return (err); } err = bus_setup_intr(dev, sc->ires, INTR_TYPE_CLK | INTR_MPSAFE, gpiopps_ifltr, gpiopps_ithrd, sc, &sc->ihandler); if (err != 0) { device_printf(dev, "Unable to setup pps irq handler\n"); gpiopps_detach(dev); return (err); } /* Create the RFC 2783 pps-api cdev. */ make_dev_args_init(&devargs); devargs.mda_devsw = &pps_cdevsw; devargs.mda_uid = UID_ROOT; devargs.mda_gid = GID_WHEEL; devargs.mda_mode = 0660; devargs.mda_si_drv1 = sc; err = make_dev_s(&devargs, &sc->pps_cdev, PPS_CDEV_NAME "%d", device_get_unit(dev)); if (err != 0) { device_printf(dev, "Unable to create pps cdev\n"); gpiopps_detach(dev); return (err); } return (0); } static int gpiopps_fdt_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { device_set_desc(dev, "GPIO PPS"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static device_method_t pps_fdt_methods[] = { DEVMETHOD(device_probe, gpiopps_fdt_probe), DEVMETHOD(device_attach, gpiopps_fdt_attach), DEVMETHOD(device_detach, gpiopps_detach), DEVMETHOD_END }; static driver_t pps_fdt_driver = { "gpiopps", pps_fdt_methods, sizeof(struct pps_softc), }; DRIVER_MODULE(gpiopps, simplebus, pps_fdt_driver, pps_devclass, 0, 0); -SIMPLEBUS_PNP_INFO(compat_data); #endif /* FDT */ diff --git a/sys/dev/gpio/gpioths.c b/sys/dev/gpio/gpioths.c index 97831db881ae..c08d772ddb9b 100644 --- a/sys/dev/gpio/gpioths.c +++ b/sys/dev/gpio/gpioths.c @@ -1,419 +1,417 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2016 Michael Zhilin All rights reserved. * Copyright (c) 2019 Ian Lepore * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * GPIOTHS - Temp/Humidity sensor over GPIO. * * This is driver for Temperature & Humidity sensor which provides digital * output over single-wire protocol from embedded 8-bit microcontroller. * Note that it uses a custom single-wire protocol, it is not 1-wire(tm). * * This driver supports the following chips: * DHT11: Temp 0c to 50c +-2.0c, Humidity 20% to 90% +-5% * DHT12: Temp -20c to 60c +-0.5c, Humidity 20% to 95% +-5% * DHT21: Temp -40c to 80c +-0.3c, Humidity 0% to 100% +-3% * DHT22: Temp -40c to 80c +-0.3c, Humidity 0% to 100% +-2% * AM2301: Same as DHT21, but also supports i2c interface. * AM2302: Same as DHT22, but also supports i2c interface. * * Temp/Humidity sensor can't be discovered automatically, please specify hints * as part of loader or kernel configuration: * hint.gpioths.0.at="gpiobus0" * hint.gpioths.0.pins= * * Or configure via FDT data. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include static struct ofw_compat_data compat_data[] = { {"dht11", true}, {NULL, false} }; +OFWBUS_PNP_INFO(compat_data); +SIMPLEBUS_PNP_INFO(compat_data); #endif /* FDT */ #define PIN_IDX 0 /* Use the first/only configured pin. */ #define GPIOTHS_POLLTIME 5 /* in seconds */ #define GPIOTHS_DHT_STARTCYCLE 20000 /* 20ms = 20000us */ #define GPIOTHS_DHT_TIMEOUT 1000 /* 1ms = 1000us */ #define GPIOTHS_DHT_CYCLES 41 #define GPIOTHS_DHT_ONEBYTEMASK 0xFF struct gpioths_softc { device_t dev; gpio_pin_t pin; int temp; int hum; int fails; struct timeout_task task; bool detaching; }; static int gpioths_probe(device_t dev) { int rv; /* * By default we only bid to attach if specifically added by our parent * (usually via hint.gpioths.#.at=busname). On FDT systems we bid as * the default driver based on being configured in the FDT data. */ rv = BUS_PROBE_NOWILDCARD; #ifdef FDT if (ofw_bus_status_okay(dev) && ofw_bus_search_compatible(dev, compat_data)->ocd_data) rv = BUS_PROBE_DEFAULT; #endif device_set_desc(dev, "DHT11/DHT22 Temperature and Humidity Sensor"); return (rv); } static int gpioths_dht_timeuntil(struct gpioths_softc *sc, bool lev, uint32_t *time) { bool cur_level; int i; for (i = 0; i < GPIOTHS_DHT_TIMEOUT; i++) { gpio_pin_is_active(sc->pin, &cur_level); if (cur_level == lev) { if (time != NULL) *time = i; return (0); } DELAY(1); } /* Timeout */ return (ETIMEDOUT); } static void gpioths_dht_initread(struct gpioths_softc *sc) { /* * According to specifications we need to drive the data line low for at * least 20ms then drive it high, to wake up the chip and signal it to * send a measurement. After sending this start signal, we switch the * pin back to input so the device can begin talking to us. */ gpio_pin_setflags(sc->pin, GPIO_PIN_OUTPUT); gpio_pin_set_active(sc->pin, false); pause_sbt("gpioths", ustosbt(GPIOTHS_DHT_STARTCYCLE), C_PREL(2), 0); gpio_pin_set_active(sc->pin, true); gpio_pin_setflags(sc->pin, GPIO_PIN_INPUT); } static int gpioths_dht_readbytes(struct gpioths_softc *sc) { uint32_t calibrations[GPIOTHS_DHT_CYCLES]; uint32_t intervals[GPIOTHS_DHT_CYCLES]; uint32_t err, avglen, value; uint8_t crc, calc; int i, negmul, offset, size, tmphi, tmplo; gpioths_dht_initread(sc); err = gpioths_dht_timeuntil(sc, false, NULL); if (err) { device_printf(sc->dev, "err(START) = %d\n", err); goto error; } /* reading - 41 cycles */ for (i = 0; i < GPIOTHS_DHT_CYCLES; i++) { err = gpioths_dht_timeuntil(sc, true, &calibrations[i]); if (err) { device_printf(sc->dev, "err(CAL, %d) = %d\n", i, err); goto error; } err = gpioths_dht_timeuntil(sc, false, &intervals[i]); if (err) { device_printf(sc->dev, "err(INTERVAL, %d) = %d\n", i, err); goto error; } } /* Calculate average data calibration cycle length */ avglen = 0; for (i = 1; i < GPIOTHS_DHT_CYCLES; i++) avglen += calibrations[i]; avglen = avglen / (GPIOTHS_DHT_CYCLES - 1); /* Calculate data */ value = 0; offset = 1; size = sizeof(value) * 8; for (i = offset; i < size + offset; i++) { value <<= 1; if (intervals[i] > avglen) value += 1; } /* Calculate CRC */ crc = 0; offset = sizeof(value) * 8 + 1; size = sizeof(crc) * 8; for (i = offset; i < size + offset; i++) { crc <<= 1; if (intervals[i] > avglen) crc += 1; } calc = 0; for (i = 0; i < sizeof(value); i++) calc += (value >> (8*i)) & GPIOTHS_DHT_ONEBYTEMASK; #ifdef GPIOTHS_DEBUG /* Debug bits */ for (i = 0; i < GPIOTHS_DHT_CYCLES; i++) device_printf(sc->dev, "%d: %d %d\n", i, calibrations[i], intervals[i]); device_printf(sc->dev, "len=%d, data=%x, crc=%x/%x\n", avglen, value, crc, calc); #endif /* GPIOTHS_DEBUG */ /* CRC check */ if (calc != crc) { err = -1; goto error; } /* * For DHT11/12, the values are split into 8 bits of integer and 8 bits * of fractional tenths. On DHT11 the fraction bytes are always zero. * On DHT12 the sign bit is in the high bit of the fraction byte. * - DHT11: 0HHHHHHH 00000000 00TTTTTT 00000000 * - DHT12: 0HHHHHHH 0000hhhh 00TTTTTT s000tttt * * For DHT21/21, the values are are encoded in 16 bits each, with the * temperature sign bit in the high bit. The values are tenths of a * degree C and tenths of a percent RH. * - DHT21: 000000HH HHHHHHHH s00000TT TTTTTTTT * - DHT22: 000000HH HHHHHHHH s00000TT TTTTTTTT * * For all devices, some bits are always zero because of the range of * values supported by the device. * * We figure out how to decode things based on the high byte of the * humidity. A DHT21/22 cannot report a value greater than 3 in * the upper bits of its 16-bit humidity. A DHT11/12 should not report * a value lower than 20. To allow for the possibility that a device * could report a value slightly out of its sensitivity range, we split * the difference and say if the value is greater than 10 it must be a * DHT11/12 (that would be a humidity over 256% on a DHT21/22). */ #define DK_OFFSET 2731 /* Offset between K and C, in decikelvins. */ if ((value >> 24) > 10) { /* DHT11 or DHT12 */ tmphi = (value >> 8) & 0x3f; tmplo = value & 0x0f; negmul = (value & 0x80) ? -1 : 1; sc->temp = DK_OFFSET + (negmul * (tmphi * 10 + tmplo)); sc->hum = (value >> 24) & 0x7f; } else { /* DHT21 or DHT22 */ negmul = (value & 0x8000) ? -1 : 1; sc->temp = DK_OFFSET + (negmul * (value & 0x03ff)); sc->hum = ((value >> 16) & 0x03ff) / 10; } sc->fails = 0; #ifdef GPIOTHS_DEBUG /* Debug bits */ device_printf(dev, "fails=%d, temp=%d, hum=%d\n", sc->fails, sc->temp, sc->hum); #endif /* GPIOTHS_DEBUG */ return (0); error: sc->fails++; return (err); } static void gpioths_poll(void *arg, int pending __unused) { struct gpioths_softc *sc; sc = (struct gpioths_softc *)arg; gpioths_dht_readbytes(sc); if (!sc->detaching) taskqueue_enqueue_timeout_sbt(taskqueue_thread, &sc->task, GPIOTHS_POLLTIME * SBT_1S, 0, C_PREL(3)); } static int gpioths_attach(device_t dev) { struct gpioths_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; int err; sc = device_get_softc(dev); ctx = device_get_sysctl_ctx(dev); tree = device_get_sysctl_tree(dev); sc->dev = dev; TIMEOUT_TASK_INIT(taskqueue_thread, &sc->task, 0, gpioths_poll, sc); #ifdef FDT /* Try to configure our pin from fdt data on fdt-based systems. */ err = gpio_pin_get_by_ofw_idx(dev, ofw_bus_get_node(dev), PIN_IDX, &sc->pin); #else err = ENOENT; #endif /* * If we didn't get configured by fdt data and our parent is gpiobus, * see if we can be configured by the bus (allows hinted attachment even * on fdt-based systems). */ if (err != 0 && strcmp("gpiobus", device_get_name(device_get_parent(dev))) == 0) err = gpio_pin_get_by_child_index(dev, PIN_IDX, &sc->pin); /* If we didn't get configured by either method, whine and punt. */ if (err != 0) { device_printf(sc->dev, "cannot acquire gpio pin (config error)\n"); return (err); } /* * Ensure we have control of our pin, and preset the data line to its * idle condition (high). Leave the line in input mode, relying on the * external pullup to keep the line high while idle. */ err = gpio_pin_setflags(sc->pin, GPIO_PIN_OUTPUT); if (err != 0) { device_printf(dev, "gpio_pin_setflags(OUT) = %d\n", err); return (err); } err = gpio_pin_set_active(sc->pin, true); if (err != 0) { device_printf(dev, "gpio_pin_set_active(false) = %d\n", err); return (err); } err = gpio_pin_setflags(sc->pin, GPIO_PIN_INPUT); if (err != 0) { device_printf(dev, "gpio_pin_setflags(IN) = %d\n", err); return (err); } /* * Do an initial read so we have correct values for reporting before * registering the sysctls that can access those values. This also * schedules the periodic polling the driver does every few seconds to * update the sysctl variables. */ gpioths_poll(sc, 0); sysctl_add_oid(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "temperature", \ CTLFLAG_RD | CTLTYPE_INT | CTLFLAG_MPSAFE, &sc->temp, 0, sysctl_handle_int, "IK", "temperature", NULL); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "humidity", CTLFLAG_RD, &sc->hum, 0, "relative humidity(%)"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "fails", CTLFLAG_RD, &sc->fails, 0, "failures since last successful read"); return (0); } static int gpioths_detach(device_t dev) { struct gpioths_softc *sc; sc = device_get_softc(dev); gpio_pin_release(sc->pin); sc->detaching = true; while (taskqueue_cancel_timeout(taskqueue_thread, &sc->task, NULL) != 0) taskqueue_drain_timeout(taskqueue_thread, &sc->task); return (0); } /* Driver bits */ static device_method_t gpioths_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gpioths_probe), DEVMETHOD(device_attach, gpioths_attach), DEVMETHOD(device_detach, gpioths_detach), DEVMETHOD_END }; static devclass_t gpioths_devclass; DEFINE_CLASS_0(gpioths, gpioths_driver, gpioths_methods, sizeof(struct gpioths_softc)); #ifdef FDT DRIVER_MODULE(gpioths, simplebus, gpioths_driver, gpioths_devclass, 0, 0); -SIMPLEBUS_PNP_INFO(compat_data); #endif DRIVER_MODULE(gpioths, gpiobus, gpioths_driver, gpioths_devclass, 0, 0); -#ifdef FDT -OFWBUS_PNP_INFO(compat_data); -#endif MODULE_DEPEND(gpioths, gpiobus, 1, 1, 1); diff --git a/sys/dev/iicbus/ads111x.c b/sys/dev/iicbus/ads111x.c index 0464de1800c0..5d7057d99b91 100644 --- a/sys/dev/iicbus/ads111x.c +++ b/sys/dev/iicbus/ads111x.c @@ -1,615 +1,613 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Ian Lepore. * * 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. */ /* * Driver for Texas Instruments ADS101x and ADS111x family i2c ADC chips. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #endif #include #include #include "iicbus_if.h" /* * Chip registers, bit definitions, shifting and masking values. */ #define ADS111x_CONV 0 /* Reg 0: Latest sample (ro) */ #define ADS111x_CONF 1 /* Reg 1: Config (rw) */ #define ADS111x_CONF_OS_SHIFT 15 /* Operational state */ #define ADS111x_CONF_MUX_SHIFT 12 /* Input mux setting */ #define ADS111x_CONF_GAIN_SHIFT 9 /* Programmable gain amp */ #define ADS111x_CONF_MODE_SHIFT 8 /* Operational mode */ #define ADS111x_CONF_RATE_SHIFT 5 /* Sample rate */ #define ADS111x_CONF_COMP_DISABLE 3 /* Comparator disable */ #define ADS111x_LOTHRESH 2 /* Compare lo threshold (rw) */ #define ADS111x_HITHRESH 3 /* Compare hi threshold (rw) */ /* * On config write, the operational-state bit starts a measurement, on read it * indicates when the measurement process is complete/idle. */ #define ADS111x_CONF_MEASURE (1u << ADS111x_CONF_OS_SHIFT) #define ADS111x_CONF_IDLE (1u << ADS111x_CONF_OS_SHIFT) /* * The default values for config items that are not per-channel. Mostly, this * turns off the comparator on chips that have that feature, because this driver * doesn't support it directly. However, the user is allowed to enable the * comparator and we'll leave it alone if they do. That allows them connect the * alert pin to something and use the feature without any help from this driver. */ #define ADS111x_CONF_DEFAULT \ ((1 << ADS111x_CONF_MODE_SHIFT) | ADS111x_CONF_COMP_DISABLE) #define ADS111x_CONF_USERMASK 0x001f /* * Per-channel defaults. The chip only has one control register, and we load * per-channel values into it every time we make a measurement on that channel. * These are the default values for the control register from the datasheet, for * values we maintain on a per-channel basis. */ #define DEFAULT_GAINIDX 2 #define DEFAULT_RATEIDX 4 /* * Full-scale ranges for each available amplifier setting, in microvolts. The * ADS1x13 chips are fixed-range, the other chips contain a programmable gain * amplifier, and the full scale range is based on the amplifier setting. */ static const u_int fixedranges[8] = { 2048000, 2048000, 2048000, 2048000, 2048000, 2048000, 2048000, 2048000, }; static const u_int gainranges[8] = { 6144000, 4096000, 2048000, 1024000, 512000, 256000, 256000, 256000, }; /* The highest value for the ADS101x chip is 0x7ff0, for ADS111x it's 0x7fff. */ #define ADS101x_RANGEDIV ((1 << 15) - 15) #define ADS111x_RANGEDIV ((1 << 15) - 1) /* Samples per second; varies based on chip type. */ static const u_int rates101x[8] = {128, 250, 490, 920, 1600, 2400, 3300, 3300}; static const u_int rates111x[8] = { 8, 16, 32, 64, 128, 250, 475, 860}; struct ads111x_channel { u_int gainidx; /* Amplifier (full-scale range) config index */ u_int rateidx; /* Samples per second config index */ bool configured; /* Channel has been configured */ }; #define ADS111x_MAX_CHANNELS 8 struct ads111x_chipinfo { const char *name; const u_int *rangetab; const u_int *ratetab; u_int numchan; int rangediv; }; static struct ads111x_chipinfo ads111x_chip_infos[] = { { "ADS1013", fixedranges, rates101x, 1, ADS101x_RANGEDIV }, { "ADS1014", gainranges, rates101x, 1, ADS101x_RANGEDIV }, { "ADS1015", gainranges, rates101x, 8, ADS101x_RANGEDIV }, { "ADS1113", fixedranges, rates111x, 1, ADS111x_RANGEDIV }, { "ADS1114", gainranges, rates111x, 1, ADS111x_RANGEDIV }, { "ADS1115", gainranges, rates111x, 8, ADS111x_RANGEDIV }, }; #ifdef FDT static struct ofw_compat_data compat_data[] = { {"ti,ads1013", (uintptr_t)&ads111x_chip_infos[0]}, {"ti,ads1014", (uintptr_t)&ads111x_chip_infos[1]}, {"ti,ads1015", (uintptr_t)&ads111x_chip_infos[2]}, {"ti,ads1113", (uintptr_t)&ads111x_chip_infos[3]}, {"ti,ads1114", (uintptr_t)&ads111x_chip_infos[4]}, {"ti,ads1115", (uintptr_t)&ads111x_chip_infos[5]}, {NULL, (uintptr_t)NULL}, }; +IICBUS_FDT_PNP_INFO(compat_data); #endif struct ads111x_softc { device_t dev; struct sx lock; int addr; int cfgword; const struct ads111x_chipinfo *chipinfo; struct ads111x_channel channels[ADS111x_MAX_CHANNELS]; }; static int ads111x_write_2(struct ads111x_softc *sc, int reg, int val) { uint8_t data[3]; struct iic_msg msgs[1]; uint8_t slaveaddr; slaveaddr = iicbus_get_addr(sc->dev); data[0] = reg; be16enc(&data[1], val); msgs[0].slave = slaveaddr; msgs[0].flags = IIC_M_WR; msgs[0].len = sizeof(data); msgs[0].buf = data; return (iicbus_transfer_excl(sc->dev, msgs, nitems(msgs), IIC_WAIT)); } static int ads111x_read_2(struct ads111x_softc *sc, int reg, int *val) { int err; uint8_t data[2]; err = iic2errno(iicdev_readfrom(sc->dev, reg, data, 2, IIC_WAIT)); if (err == 0) *val = (int16_t)be16dec(data); return (err); } static int ads111x_sample_voltage(struct ads111x_softc *sc, int channum, int *voltage) { struct ads111x_channel *chan; int err, cfgword, convword, rate, retries, waitns; int64_t fsrange; chan = &sc->channels[channum]; /* Ask the chip to do a one-shot measurement of the given channel. */ cfgword = sc->cfgword | (1 << ADS111x_CONF_OS_SHIFT) | (channum << ADS111x_CONF_MUX_SHIFT) | (chan->gainidx << ADS111x_CONF_GAIN_SHIFT) | (chan->rateidx << ADS111x_CONF_RATE_SHIFT); if ((err = ads111x_write_2(sc, ADS111x_CONF, cfgword)) != 0) return (err); /* * Calculate how long it will take to make the measurement at the * current sampling rate (round up). The measurement averaging time * ranges from 300us to 125ms, so we yield the cpu while waiting. */ rate = sc->chipinfo->ratetab[chan->rateidx]; waitns = (1000000000 + rate - 1) / rate; err = pause_sbt("ads111x", nstosbt(waitns), 0, C_PREL(2)); if (err != 0 && err != EWOULDBLOCK) return (err); /* * In theory the measurement should be available now; we waited long * enough. However, the chip times its averaging intervals using an * internal 1 MHz oscillator which likely isn't running at the same rate * as the system clock, so we have to double-check that the measurement * is complete before reading the result. If it's not ready yet, yield * the cpu again for 5% of the time we originally calculated. * * Unlike most i2c slaves, this device does not auto-increment the * register number on reads, so we can't read both status and * measurement in one operation. */ for (retries = 5; ; --retries) { if (retries == 0) return (EWOULDBLOCK); if ((err = ads111x_read_2(sc, ADS111x_CONF, &cfgword)) != 0) return (err); if (cfgword & ADS111x_CONF_IDLE) break; pause_sbt("ads111x", nstosbt(waitns / 20), 0, C_PREL(2)); } /* Retrieve the sample and convert it to microvolts. */ if ((err = ads111x_read_2(sc, ADS111x_CONV, &convword)) != 0) return (err); fsrange = sc->chipinfo->rangetab[chan->gainidx]; *voltage = (int)((convword * fsrange ) / sc->chipinfo->rangediv); return (err); } static int ads111x_sysctl_gainidx(SYSCTL_HANDLER_ARGS) { struct ads111x_softc *sc; int chan, err, gainidx; sc = arg1; chan = arg2; gainidx = sc->channels[chan].gainidx; err = sysctl_handle_int(oidp, &gainidx, 0, req); if (err != 0 || req->newptr == NULL) return (err); if (gainidx < 0 || gainidx > 7) return (EINVAL); sx_xlock(&sc->lock); sc->channels[chan].gainidx = gainidx; sx_xunlock(&sc->lock); return (err); } static int ads111x_sysctl_rateidx(SYSCTL_HANDLER_ARGS) { struct ads111x_softc *sc; int chan, err, rateidx; sc = arg1; chan = arg2; rateidx = sc->channels[chan].rateidx; err = sysctl_handle_int(oidp, &rateidx, 0, req); if (err != 0 || req->newptr == NULL) return (err); if (rateidx < 0 || rateidx > 7) return (EINVAL); sx_xlock(&sc->lock); sc->channels[chan].rateidx = rateidx; sx_xunlock(&sc->lock); return (err); } static int ads111x_sysctl_voltage(SYSCTL_HANDLER_ARGS) { struct ads111x_softc *sc; int chan, err, voltage; sc = arg1; chan = arg2; if (req->oldptr != NULL) { sx_xlock(&sc->lock); err = ads111x_sample_voltage(sc, chan, &voltage); sx_xunlock(&sc->lock); if (err != 0) { device_printf(sc->dev, "conversion read failed, error %d\n", err); return (err); } } err = sysctl_handle_int(oidp, &voltage, 0, req); return (err); } static int ads111x_sysctl_config(SYSCTL_HANDLER_ARGS) { struct ads111x_softc *sc; int config, err; sc = arg1; config = sc->cfgword & ADS111x_CONF_USERMASK; err = sysctl_handle_int(oidp, &config, 0, req); if (err != 0 || req->newptr == NULL) return (err); sx_xlock(&sc->lock); sc->cfgword = config & ADS111x_CONF_USERMASK; err = ads111x_write_2(sc, ADS111x_CONF, sc->cfgword); sx_xunlock(&sc->lock); return (err); } static int ads111x_sysctl_lothresh(SYSCTL_HANDLER_ARGS) { struct ads111x_softc *sc; int thresh, err; sc = arg1; if ((err = ads111x_read_2(sc, ADS111x_LOTHRESH, &thresh)) != 0) return (err); err = sysctl_handle_int(oidp, &thresh, 0, req); if (err != 0 || req->newptr == NULL) return (err); sx_xlock(&sc->lock); err = ads111x_write_2(sc, ADS111x_CONF, thresh); sx_xunlock(&sc->lock); return (err); } static int ads111x_sysctl_hithresh(SYSCTL_HANDLER_ARGS) { struct ads111x_softc *sc; int thresh, err; sc = arg1; if ((err = ads111x_read_2(sc, ADS111x_HITHRESH, &thresh)) != 0) return (err); err = sysctl_handle_int(oidp, &thresh, 0, req); if (err != 0 || req->newptr == NULL) return (err); sx_xlock(&sc->lock); err = ads111x_write_2(sc, ADS111x_CONF, thresh); sx_xunlock(&sc->lock); return (err); } static void ads111x_setup_channel(struct ads111x_softc *sc, int chan, int gainidx, int rateidx) { struct ads111x_channel *c; struct sysctl_ctx_list *ctx; struct sysctl_oid *chantree, *devtree; char chanstr[4]; c = &sc->channels[chan]; c->gainidx = gainidx; c->rateidx = rateidx; /* * If setting up the channel for the first time, create channel's * sysctl entries. We might have already configured the channel if * config data for it exists in both FDT and hints. */ if (c->configured) return; ctx = device_get_sysctl_ctx(sc->dev); devtree = device_get_sysctl_tree(sc->dev); snprintf(chanstr, sizeof(chanstr), "%d", chan); chantree = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(devtree), OID_AUTO, chanstr, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "channel data"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(chantree), OID_AUTO, "gain_index", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, sc, chan, ads111x_sysctl_gainidx, "I", "programmable gain amp setting, 0-7"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(chantree), OID_AUTO, "rate_index", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, sc, chan, ads111x_sysctl_rateidx, "I", "sample rate setting, 0-7"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(chantree), OID_AUTO, "voltage", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_SKIP | CTLFLAG_NEEDGIANT, sc, chan, ads111x_sysctl_voltage, "I", "sampled voltage in microvolts"); c->configured = true; } static void ads111x_add_channels(struct ads111x_softc *sc) { const char *name; uint32_t chan, gainidx, num_added, rateidx, unit; bool found; #ifdef FDT phandle_t child, node; /* Configure any channels that have FDT data. */ num_added = 0; node = ofw_bus_get_node(sc->dev); for (child = OF_child(node); child != 0; child = OF_peer(child)) { if (OF_getencprop(child, "reg", &chan, sizeof(chan)) == -1) continue; if (chan >= ADS111x_MAX_CHANNELS) continue; gainidx = DEFAULT_GAINIDX; rateidx = DEFAULT_RATEIDX; OF_getencprop(child, "ti,gain", &gainidx, sizeof(gainidx)); OF_getencprop(child, "ti,datarate", &rateidx, sizeof(rateidx)); ads111x_setup_channel(sc, chan, gainidx, rateidx); ++num_added; } #else num_added = 0; #endif /* Configure any channels that have hint data. */ name = device_get_name(sc->dev); unit = device_get_unit(sc->dev); for (chan = 0; chan < sc->chipinfo->numchan; ++chan) { char resname[16]; found = false; gainidx = DEFAULT_GAINIDX; rateidx = DEFAULT_RATEIDX; snprintf(resname, sizeof(resname), "%d.gain_index", chan); if (resource_int_value(name, unit, resname, &gainidx) == 0) found = true; snprintf(resname, sizeof(resname), "%d.rate_index", chan); if (resource_int_value(name, unit, resname, &rateidx) == 0) found = true; if (found) { ads111x_setup_channel(sc, chan, gainidx, rateidx); ++num_added; } } /* If any channels were configured via FDT or hints, we're done. */ if (num_added > 0) return; /* * No channel config; add all possible channels using default values, * and let the user configure the ones they want on the fly via sysctl. */ for (chan = 0; chan < sc->chipinfo->numchan; ++chan) { gainidx = DEFAULT_GAINIDX; rateidx = DEFAULT_RATEIDX; ads111x_setup_channel(sc, chan, gainidx, rateidx); } } static const struct ads111x_chipinfo * ads111x_find_chipinfo(device_t dev) { const struct ads111x_chipinfo *info; const char *chiptype; int i; #ifdef FDT if (ofw_bus_status_okay(dev)) { info = (struct ads111x_chipinfo*) ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (info != NULL) return (info); } #endif /* For hinted devices, we must be told the chip type. */ chiptype = NULL; resource_string_value(device_get_name(dev), device_get_unit(dev), "type", &chiptype); if (chiptype != NULL) { for (i = 0; i < nitems(ads111x_chip_infos); ++i) { info = &ads111x_chip_infos[i]; if (strcasecmp(chiptype, info->name) == 0) return (info); } } return (NULL); } static int ads111x_probe(device_t dev) { const struct ads111x_chipinfo *info; info = ads111x_find_chipinfo(dev); if (info != NULL) { device_set_desc(dev, info->name); #ifdef FDT return (BUS_PROBE_DEFAULT); #else return (BUS_PROBE_NOWILDCARD); #endif } return (ENXIO); } static int ads111x_attach(device_t dev) { struct ads111x_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; int err; sc = device_get_softc(dev); sc->dev = dev; sc->addr = iicbus_get_addr(dev); sc->cfgword = ADS111x_CONF_DEFAULT; sc->chipinfo = ads111x_find_chipinfo(sc->dev); if (sc->chipinfo == NULL) { device_printf(dev, "cannot get chipinfo (but it worked during probe)"); return (ENXIO); } /* Set the default chip config. */ if ((err = ads111x_write_2(sc, ADS111x_CONF, sc->cfgword)) != 0) { device_printf(dev, "cannot write chip config register\n"); return (err); } /* Add the sysctl handler to set the chip configuration register. */ ctx = device_get_sysctl_ctx(dev); tree = device_get_sysctl_tree(dev); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "config", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, sc, 0, ads111x_sysctl_config, "I", "configuration register word"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "lo_thresh", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, sc, 0, ads111x_sysctl_lothresh, "I", "comparator low threshold"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "hi_thresh", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, sc, 0, ads111x_sysctl_hithresh, "I", "comparator high threshold"); /* Set up channels based on metadata or default config. */ ads111x_add_channels(sc); sx_init(&sc->lock, "ads111x"); return (0); } static int ads111x_detach(device_t dev) { struct ads111x_softc *sc; sc = device_get_softc(dev); sx_destroy(&sc->lock); return (0); } static device_method_t ads111x_methods[] = { DEVMETHOD(device_probe, ads111x_probe), DEVMETHOD(device_attach, ads111x_attach), DEVMETHOD(device_detach, ads111x_detach), DEVMETHOD_END, }; static driver_t ads111x_driver = { "ads111x", ads111x_methods, sizeof(struct ads111x_softc), }; static devclass_t ads111x_devclass; DRIVER_MODULE(ads111x, iicbus, ads111x_driver, ads111x_devclass, NULL, NULL); -#ifdef FDT -IICBUS_FDT_PNP_INFO(compat_data); -#endif MODULE_VERSION(ads111x, 1); MODULE_DEPEND(ads111x, iicbus, 1, 1, 1); diff --git a/sys/dev/iicbus/mux/iic_gpiomux.c b/sys/dev/iicbus/mux/iic_gpiomux.c index 9d34d21c6572..8e064d84619d 100644 --- a/sys/dev/iicbus/mux/iic_gpiomux.c +++ b/sys/dev/iicbus/mux/iic_gpiomux.c @@ -1,271 +1,267 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Ian Lepore * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Driver for i2c bus muxes controlled by one or more gpio pins. * * This driver has #ifdef FDT sections in it, as if it supports both fdt and * hinted attachment, but there is currently no support for hinted attachment. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include static struct ofw_compat_data compat_data[] = { {"i2c-mux-gpio", true}, {NULL, false} }; +OFWBUS_PNP_INFO(compat_data); +SIMPLEBUS_PNP_INFO(compat_data); #endif /* FDT */ #include #include "iicmux.h" #include "iicmux_if.h" struct gpiomux_softc { struct iicmux_softc mux; int idleidx; int numpins; gpio_pin_t pins[IICMUX_MAX_BUSES]; }; #define IDLE_NOOP (-1) /* When asked to idle the bus, do nothing. */ static int gpiomux_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd) { struct gpiomux_softc *sc = device_get_softc(dev); int i; /* * The iicmux caller ensures busidx is between 0 and the number of buses * we passed to iicmux_init_softc(), no need for validation here. The * bits in the index number are transcribed to the state of the pins, * except when we're asked to idle the bus. In that case, we transcribe * sc->idleidx to the pins, unless that is IDLE_NOOP (leave the current * bus selected), in which case we just bail. */ if (busidx == IICMUX_SELECT_IDLE) { if (sc->idleidx == IDLE_NOOP) return (0); busidx = sc->idleidx; } for (i = 0; i < sc->numpins; ++i) gpio_pin_set_active(sc->pins[i], busidx & (1u << i)); return (0); } static int gpiomux_probe(device_t dev) { int rv; rv = ENXIO; #ifdef FDT if (ofw_bus_status_okay(dev) && ofw_bus_search_compatible(dev, compat_data)->ocd_data) rv = BUS_PROBE_DEFAULT; #endif device_set_desc(dev, "I2C GPIO Mux"); return (rv); } static void gpiomux_release_pins(struct gpiomux_softc *sc) { int i; for (i = 0; i < sc->numpins; ++i) gpio_pin_release(sc->pins[i]); } static int gpiomux_attach(device_t dev) { struct gpiomux_softc *sc = device_get_softc(dev); ssize_t len; device_t busdev; int err, i, idlebits, numchannels; pcell_t propval; phandle_t node; node = ofw_bus_get_node(dev); /* * Locate the gpio pin(s) that control the mux hardware. There can be * multiple pins, but there must be at least one. */ for (i = 0; ; ++i) { err = gpio_pin_get_by_ofw_propidx(dev, node, "mux-gpios", i, &sc->pins[i]); if (err != 0) { break; } } sc->numpins = i; if (sc->numpins == 0) { device_printf(dev, "cannot acquire pins listed in mux-gpios\n"); if (err == 0) err = ENXIO; goto errexit; } numchannels = 1u << sc->numpins; if (numchannels > IICMUX_MAX_BUSES) { device_printf(dev, "too many mux-gpios pins for max %d buses\n", IICMUX_MAX_BUSES); err = EINVAL; goto errexit; } /* * We don't have a parent/child relationship to the upstream bus, we * have to locate it via the i2c-parent property. Explicitly tell the * user which upstream we're associated with, since the normal attach * message is going to mention only our actual parent. */ len = OF_getencprop(node, "i2c-parent", &propval, sizeof(propval)); if (len != sizeof(propval)) { device_printf(dev, "cannot obtain i2c-parent property\n"); err = ENXIO; goto errexit; } busdev = OF_device_from_xref((phandle_t)propval); if (busdev == NULL) { device_printf(dev, "cannot find device referenced by i2c-parent property\n"); err = ENXIO; goto errexit; } device_printf(dev, "upstream bus is %s\n", device_get_nameunit(busdev)); /* * If there is an idle-state property, that is the value we set the pins * to when the bus is idle, otherwise idling the bus is a no-op * (whichever bus was last accessed remains active). */ len = OF_getencprop(node, "idle-state", &propval, sizeof(propval)); if (len == sizeof(propval)) { if ((int)propval >= numchannels) { device_printf(dev, "idle-state property %d exceeds channel count\n", propval); } sc->idleidx = (int)propval; idlebits = sc->idleidx; } else { sc->idleidx = IDLE_NOOP; idlebits = 0; } /* Preset the mux to the idle state to get things started. */ for (i = 0; i < sc->numpins; ++i) { gpio_pin_setflags(sc->pins[i], GPIO_PIN_OUTPUT); gpio_pin_set_active(sc->pins[i], idlebits & (1u << i)); } /* Init the core driver, have it add our child downstream buses. */ if ((err = iicmux_attach(dev, busdev, numchannels)) == 0) bus_generic_attach(dev); errexit: if (err != 0) gpiomux_release_pins(sc); return (err); } static int gpiomux_detach(device_t dev) { struct gpiomux_softc *sc = device_get_softc(dev); int err; if ((err = iicmux_detach(dev)) != 0) return (err); gpiomux_release_pins(sc); return (0); } static device_method_t gpiomux_methods[] = { /* device methods */ DEVMETHOD(device_probe, gpiomux_probe), DEVMETHOD(device_attach, gpiomux_attach), DEVMETHOD(device_detach, gpiomux_detach), /* iicmux methods */ DEVMETHOD(iicmux_bus_select, gpiomux_bus_select), DEVMETHOD_END }; static devclass_t gpiomux_devclass; DEFINE_CLASS_1(iic_gpiomux, iic_gpiomux_driver, gpiomux_methods, sizeof(struct gpiomux_softc), iicmux_driver); DRIVER_MODULE(iic_gpiomux, simplebus, iic_gpiomux_driver, gpiomux_devclass, 0, 0); -#ifdef FDT -SIMPLEBUS_PNP_INFO(compat_data); -#endif DRIVER_MODULE(iic_gpiomux, ofw_simplebus, iic_gpiomux_driver, gpiomux_devclass, 0, 0); -#ifdef FDT -OFWBUS_PNP_INFO(compat_data); -#endif #ifdef FDT DRIVER_MODULE(ofw_iicbus, iic_gpiomux, ofw_iicbus_driver, ofw_iicbus_devclass, 0, 0); #else DRIVER_MODULE(iicbus, iic_gpiomux, iicbus_driver, iicbus_devclass, 0, 0); #endif MODULE_DEPEND(iic_gpiomux, iicmux, 1, 1, 1); MODULE_DEPEND(iic_gpiomux, iicbus, 1, 1, 1); diff --git a/sys/dev/iicbus/mux/ltc430x.c b/sys/dev/iicbus/mux/ltc430x.c index 77f1fafe6fe1..fab791ed3174 100644 --- a/sys/dev/iicbus/mux/ltc430x.c +++ b/sys/dev/iicbus/mux/ltc430x.c @@ -1,256 +1,258 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Ian Lepore * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include "iicbus_if.h" #include "iicmux_if.h" static struct chip_info { const char *partname; const char *description; int numchannels; } chip_infos[] = { {"ltc4305", "LTC4305 I2C Mux", 2}, {"ltc4306", "LTC4306 I2C Mux", 4}, }; #define CHIP_NONE (-1) #define CHIP_LTC4305 0 #define CHIP_LTC4306 1 #ifdef FDT #include #include #include static struct ofw_compat_data compat_data[] = { {"lltc,ltc4305", CHIP_LTC4305}, {"lltc,ltc4306", CHIP_LTC4306}, {NULL, CHIP_NONE} }; +IICBUS_FDT_PNP_INFO(compat_data); #endif #include struct ltc430x_softc { struct iicmux_softc mux; bool idle_disconnect; }; /* * The datasheet doesn't name the registers, it calls them control register 0-3. */ #define LTC430X_CTLREG_0 0 #define LTC430X_CTLREG_1 1 #define LTC430X_CTLREG_2 2 #define LTC430X_CR2_ENABLE_MW (1u << 3) /* Enable mass write address. */ #define LTC430X_CTLREG_3 3 static int ltc430x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd) { struct ltc430x_softc *sc = device_get_softc(dev); uint8_t busbits; /* * The iicmux caller ensures busidx is between 0 and the number of buses * we passed to iicmux_init_softc(), no need for validation here. If * the fdt data has the idle_disconnect property we idle the bus by * selecting no downstream buses, otherwise we just leave the current * bus active. The upper bits of control register 3 activate the * downstream buses; bit 7 is the first bus, bit 6 the second, etc. */ if (busidx == IICMUX_SELECT_IDLE) { if (sc->idle_disconnect) busbits = 0; else return (0); } else { busbits = 1u << (7 - busidx); } /* * We have to add the IIC_RECURSIVE flag because the iicmux core has * already reserved the bus for us, and iicdev_writeto() is going to try * to reserve it again, which is allowed with the recursive flag. */ return (iicdev_writeto(dev, LTC430X_CTLREG_3, &busbits, sizeof(busbits), rd->flags | IIC_RECURSIVE)); } static int ltc430x_find_chiptype(device_t dev) { #ifdef FDT return (ofw_bus_search_compatible(dev, compat_data)->ocd_data); #else const char *type; int i; if (resource_string_value(device_get_name(dev), device_get_unit(dev), "chip_type", &type) == 0) { for (i = 0; i < nitems(chip_infos); ++i) { if (strcasecmp(type, chip_infos[i].partname) == 0) { return (i); } } } return (CHIP_NONE); #endif } static int ltc430x_probe(device_t dev) { int type; #ifdef FDT if (!ofw_bus_status_okay(dev)) return (ENXIO); #endif type = ltc430x_find_chiptype(dev); if (type == CHIP_NONE) return (ENXIO); device_set_desc(dev, chip_infos[type].description); return (BUS_PROBE_DEFAULT); } static int ltc430x_attach(device_t dev) { struct ltc430x_softc *sc __unused; int chip, err, numchan, val; uint8_t busbits, ctlreg2; sc = device_get_softc(dev); busbits = 0; ctlreg2 = LTC430X_CR2_ENABLE_MW; /* * Check for the idle-disconnect and ctlreg2 options, first in FDT data, * then allow them to be overriden by hints data. */ #ifdef FDT phandle_t node; node = ofw_bus_get_node(dev); sc->idle_disconnect = OF_hasprop(node, "i2c-mux-idle-disconnect"); if (OF_getprop(node, "freebsd,ctlreg2", &val, sizeof(val)) > 0) { ctlreg2 = val; } #endif if (resource_int_value(device_get_name(dev), device_get_unit(dev), "idle_disconnect", &val) == 0) { sc->idle_disconnect = val; } if (resource_int_value(device_get_name(dev), device_get_unit(dev), "ctlreg2", &val) == 0) { ctlreg2 = val; } /* We found the chip type when probing, so now it "can't fail". */ if ((chip = ltc430x_find_chiptype(dev)) == CHIP_NONE) { device_printf(dev, "impossible: can't identify chip type\n"); return (ENXIO); } numchan = chip_infos[chip].numchannels; /* Set control reg 2 with configured (or default) values. */ iicdev_writeto(dev, LTC430X_CTLREG_2, &ctlreg2, sizeof(ctlreg2), IIC_WAIT); /* If configured for idle-disconnect, ensure we start disconnected. */ if (sc->idle_disconnect) { iicdev_writeto(dev, LTC430X_CTLREG_3, &busbits, sizeof(busbits), IIC_WAIT); } /* * Invoke the iicmux framework's attach code, and if it succeeds, invoke * the probe and attach code of any child iicbus instances it added. */ if ((err = iicmux_attach(dev, device_get_parent(dev), numchan)) == 0) bus_generic_attach(dev); return (err); } static int ltc430x_detach(device_t dev) { int err; if ((err = iicmux_detach(dev)) != 0) return (err); return (0); } static device_method_t ltc430x_methods[] = { /* device methods */ DEVMETHOD(device_probe, ltc430x_probe), DEVMETHOD(device_attach, ltc430x_attach), DEVMETHOD(device_detach, ltc430x_detach), /* iicmux methods */ DEVMETHOD(iicmux_bus_select, ltc430x_bus_select), DEVMETHOD_END }; static devclass_t ltc430x_devclass; DEFINE_CLASS_1(ltc430x, ltc430x_driver, ltc430x_methods, sizeof(struct ltc430x_softc), iicmux_driver); DRIVER_MODULE(ltc430x, iicbus, ltc430x_driver, ltc430x_devclass, 0, 0); + #ifdef FDT -IICBUS_FDT_PNP_INFO(compat_data); DRIVER_MODULE(ofw_iicbus, ltc430x, ofw_iicbus_driver, ofw_iicbus_devclass, 0, 0); #else DRIVER_MODULE(iicbus, ltc430x, iicbus_driver, iicbus_devclass, 0, 0); #endif MODULE_DEPEND(ltc430x, iicmux, 1, 1, 1); MODULE_DEPEND(ltc430x, iicbus, 1, 1, 1); + diff --git a/sys/dev/iicbus/mux/pca9547.c b/sys/dev/iicbus/mux/pca9547.c index 2bdc6cb2e867..ac57f26dbb04 100644 --- a/sys/dev/iicbus/mux/pca9547.c +++ b/sys/dev/iicbus/mux/pca9547.c @@ -1,162 +1,162 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Ian Lepore * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include "iicmux_if.h" static struct ofw_compat_data compat_data[] = { {"nxp,pca9547", 1}, {NULL, 0} }; +IICBUS_FDT_PNP_INFO(compat_data); #include struct pca9547_softc { struct iicmux_softc mux; device_t dev; bool idle_disconnect; }; static int pca9547_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd) { struct pca9547_softc *sc = device_get_softc(dev); uint8_t busbits; /* * The iicmux caller ensures busidx is between 0 and the number of buses * we passed to iicmux_init_softc(), no need for validation here. If * the fdt data has the idle_disconnect property we idle the bus by * selecting no downstream buses, otherwise we just leave the current * bus active. The upper bits of control register 3 activate the * downstream buses; bit 7 is the first bus, bit 6 the second, etc. */ if (busidx == IICMUX_SELECT_IDLE) { if (sc->idle_disconnect) busbits = 0; else return (0); } else { busbits = 0x8 | (busidx & 0x7); } /* * We have to add the IIC_RECURSIVE flag because the iicmux core has * already reserved the bus for us, and iicdev_writeto() is going to try * to reserve it again, which is allowed with the recursive flag. */ return (iicdev_writeto(dev, busbits, NULL, 0, rd->flags | IIC_RECURSIVE)); } static int pca9547_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "PCA9547 IIC bus multiplexor"); return (BUS_PROBE_DEFAULT); } static int pca9547_attach(device_t dev) { struct pca9547_softc *sc; phandle_t node; int rv; sc = device_get_softc(dev); sc ->dev = dev; node = ofw_bus_get_node(dev); sc->idle_disconnect = OF_hasprop(node, "i2c-mux-idle-disconnect"); rv = iicmux_attach(sc->dev, device_get_parent(dev), 8); if (rv != 0) return (rv); rv = bus_generic_attach(dev); return (rv); } static int pca9547_detach(device_t dev) { int rv; rv = iicmux_detach(dev); if (rv != 0) return (rv); return (0); } static device_method_t pca9547_methods[] = { /* device methods */ DEVMETHOD(device_probe, pca9547_probe), DEVMETHOD(device_attach, pca9547_attach), DEVMETHOD(device_detach, pca9547_detach), /* iicmux methods */ DEVMETHOD(iicmux_bus_select, pca9547_bus_select), DEVMETHOD_END }; static devclass_t pca9547_devclass; DEFINE_CLASS_1(iicmux, pca9547_driver, pca9547_methods, sizeof(struct pca9547_softc), iicmux_driver); DRIVER_MODULE(pca_iicmux, iicbus, pca9547_driver, pca9547_devclass, 0, 0); -IICBUS_FDT_PNP_INFO(compat_data); DRIVER_MODULE(iicbus, iicmux, iicbus_driver, iicbus_devclass, 0, 0); DRIVER_MODULE(ofw_iicbus, iicmux, ofw_iicbus_driver, ofw_iicbus_devclass, 0, 0); MODULE_DEPEND(pca9547, iicmux, 1, 1, 1); MODULE_DEPEND(pca9547, iicbus, 1, 1, 1); diff --git a/sys/dev/ow/owc_gpiobus.c b/sys/dev/ow/owc_gpiobus.c index 8ed73e10862b..4b8b2ab6f99e 100644 --- a/sys/dev/ow/owc_gpiobus.c +++ b/sys/dev/ow/owc_gpiobus.c @@ -1,404 +1,402 @@ /*- * Copyright (c) 2015 M. Warner Losh * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include static struct ofw_compat_data compat_data[] = { {"w1-gpio", true}, {NULL, false} }; +OFWBUS_PNP_INFO(compat_data); +SIMPLEBUS_PNP_INFO(compat_data); #endif /* FDT */ #define OW_PIN 0 #define OWC_GPIOBUS_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define OWC_GPIOBUS_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define OWC_GPIOBUS_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \ "owc_gpiobus", MTX_DEF) #define OWC_GPIOBUS_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); struct owc_gpiobus_softc { device_t sc_dev; gpio_pin_t sc_pin; struct mtx sc_mtx; }; static int owc_gpiobus_probe(device_t); static int owc_gpiobus_attach(device_t); static int owc_gpiobus_detach(device_t); static int owc_gpiobus_probe(device_t dev) { int rv; /* * By default we only bid to attach if specifically added by our parent * (usually via hint.owc_gpiobus.#.at=busname). On FDT systems we bid * as the default driver based on being configured in the FDT data. */ rv = BUS_PROBE_NOWILDCARD; #ifdef FDT if (ofw_bus_status_okay(dev) && ofw_bus_search_compatible(dev, compat_data)->ocd_data) rv = BUS_PROBE_DEFAULT; #endif device_set_desc(dev, "GPIO one-wire bus"); return (rv); } static int owc_gpiobus_attach(device_t dev) { struct owc_gpiobus_softc *sc; int err; sc = device_get_softc(dev); sc->sc_dev = dev; #ifdef FDT /* Try to configure our pin from fdt data on fdt-based systems. */ err = gpio_pin_get_by_ofw_idx(dev, ofw_bus_get_node(dev), OW_PIN, &sc->sc_pin); #else err = ENOENT; #endif /* * If we didn't get configured by fdt data and our parent is gpiobus, * see if we can be configured by the bus (allows hinted attachment even * on fdt-based systems). */ if (err != 0 && strcmp("gpiobus", device_get_name(device_get_parent(dev))) == 0) err = gpio_pin_get_by_child_index(dev, OW_PIN, &sc->sc_pin); /* If we didn't get configured by either method, whine and punt. */ if (err != 0) { device_printf(sc->sc_dev, "cannot acquire gpio pin (config error)\n"); return (err); } OWC_GPIOBUS_LOCK_INIT(sc); /* * Add the ow bus as a child, but defer probing and attaching it until * interrupts work, because we can't do IO for them until we can read * the system timecounter (which initializes after device attachments). */ device_add_child(sc->sc_dev, "ow", -1); return (bus_delayed_attach_children(dev)); } static int owc_gpiobus_detach(device_t dev) { struct owc_gpiobus_softc *sc; int err; sc = device_get_softc(dev); if ((err = device_delete_children(dev)) != 0) return (err); gpio_pin_release(sc->sc_pin); OWC_GPIOBUS_LOCK_DESTROY(sc); return (0); } /* * In the diagrams below, R is driven by the resistor pullup, M is driven by the * master, and S is driven by the slave / target. */ /* * These macros let what why we're doing stuff shine in the code * below, and let the how be confined to here. */ #define OUTPIN(sc) gpio_pin_setflags((sc)->sc_pin, GPIO_PIN_OUTPUT) #define INPIN(sc) gpio_pin_setflags((sc)->sc_pin, GPIO_PIN_INPUT) #define GETPIN(sc, bp) gpio_pin_is_active((sc)->sc_pin, (bp)) #define LOW(sc) gpio_pin_set_active((sc)->sc_pin, false) /* * WRITE-ONE (see owll_if.m for timings) From Figure 4-1 AN-937 * * |<---------tSLOT---->|<-tREC->| * High RRRRM | RRRRRRRRRRRR|RRRRRRRRM * M | R | | | M * M| R | | | M * Low MMMMMMM | | | MMMMMM... * |<-tLOW1->| | | * |<------15us--->| | * |<--------60us---->| */ static int owc_gpiobus_write_one(device_t dev, struct ow_timing *t) { struct owc_gpiobus_softc *sc; sc = device_get_softc(dev); critical_enter(); /* Force low */ OUTPIN(sc); LOW(sc); DELAY(t->t_low1); /* Allow resistor to float line high */ INPIN(sc); DELAY(t->t_slot - t->t_low1 + t->t_rec); critical_exit(); return (0); } /* * WRITE-ZERO (see owll_if.m for timings) From Figure 4-2 AN-937 * * |<---------tSLOT------>|<-tREC->| * High RRRRM | | |RRRRRRRM * M | | R M * M| | | |R M * Low MMMMMMMMMMMMMMMMMMMMMR MMMMMM... * |<--15us->| | | * |<------60us--->| | * |<-------tLOW0------>| */ static int owc_gpiobus_write_zero(device_t dev, struct ow_timing *t) { struct owc_gpiobus_softc *sc; sc = device_get_softc(dev); critical_enter(); /* Force low */ OUTPIN(sc); LOW(sc); DELAY(t->t_low0); /* Allow resistor to float line high */ INPIN(sc); DELAY(t->t_slot - t->t_low0 + t->t_rec); critical_exit(); return (0); } /* * READ-DATA (see owll_if.m for timings) From Figure 4-3 AN-937 * * |<---------tSLOT------>|<-tREC->| * High RRRRM | rrrrrrrrrrrrrrrRRRRRRRM * M | r | R M * M| r | |R M * Low MMMMMMMSSSSSSSSSSSSSSR MMMMMM... * |< sample > | * |<------tRDV---->| | * ->| |<-tRELEASE * * r -- allowed to pull high via the resitor when slave writes a 1-bit * */ static int owc_gpiobus_read_data(device_t dev, struct ow_timing *t, int *bit) { struct owc_gpiobus_softc *sc; bool sample; sbintime_t then, now; sc = device_get_softc(dev); critical_enter(); /* Force low for t_lowr microseconds */ then = sbinuptime(); OUTPIN(sc); LOW(sc); DELAY(t->t_lowr); /* * Slave is supposed to hold the line low for t_rdv microseconds for 0 * and immediately float it high for a 1. This is measured from the * master's pushing the line low. */ INPIN(sc); do { now = sbinuptime(); GETPIN(sc, &sample); } while (now - then < (t->t_rdv + 2) * SBT_1US && sample == false); critical_exit(); if (now - then < t->t_rdv * SBT_1US) *bit = 1; else *bit = 0; /* Wait out the rest of t_slot */ do { now = sbinuptime(); } while (now - then < (t->t_slot + t->t_rec) * SBT_1US); return (0); } /* * RESET AND PRESENCE PULSE (see owll_if.m for timings) From Figure 4-4 AN-937 * * |<---------tRSTH------------>| * High RRRM | | RRRRRRRS | RRRR RRM * M | |R| |S | R M * M| R | | S |R M * Low MMMMMMMM MMMMMM| | | SSSSSSSSSS MMMMMM * |<----tRSTL--->| | |<-tPDL---->| * | ->| |<-tR | | * || * * Note: for Regular Speed operations, tRSTL + tR should be less than 960us to * avoid interfering with other devices on the bus. * * Return values in *bit: * -1 = Bus wiring error (stuck low). * 0 = no presence pulse * 1 = presence pulse detected */ static int owc_gpiobus_reset_and_presence(device_t dev, struct ow_timing *t, int *bit) { struct owc_gpiobus_softc *sc; bool sample; sc = device_get_softc(dev); /* * Read the current state of the bus. The steady state of an idle bus is * high. Badly wired buses that are missing the required pull up, or * that have a short circuit to ground cause all kinds of mischief when * we try to read them later. Return EIO if the bus is currently low. */ INPIN(sc); GETPIN(sc, &sample); if (sample == false) { *bit = -1; return (EIO); } critical_enter(); /* Force low */ OUTPIN(sc); LOW(sc); DELAY(t->t_rstl); /* Allow resistor to float line high and then wait for reset pulse */ INPIN(sc); DELAY(t->t_pdh + t->t_pdl / 2); /* Read presence pulse */ GETPIN(sc, &sample); *bit = sample; critical_exit(); DELAY(t->t_rsth - (t->t_pdh + t->t_pdl / 2)); /* Timing not critical for this one */ /* * Read the state of the bus after we've waited past the end of the rest * window. It should return to high. If it is low, then we have some * problem and should abort the reset. */ GETPIN(sc, &sample); if (sample == false) { *bit = -1; return (EIO); } return (0); } static devclass_t owc_gpiobus_devclass; static device_method_t owc_gpiobus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, owc_gpiobus_probe), DEVMETHOD(device_attach, owc_gpiobus_attach), DEVMETHOD(device_detach, owc_gpiobus_detach), DEVMETHOD(owll_write_one, owc_gpiobus_write_one), DEVMETHOD(owll_write_zero, owc_gpiobus_write_zero), DEVMETHOD(owll_read_data, owc_gpiobus_read_data), DEVMETHOD(owll_reset_and_presence, owc_gpiobus_reset_and_presence), { 0, 0 } }; static driver_t owc_gpiobus_driver = { "owc", owc_gpiobus_methods, sizeof(struct owc_gpiobus_softc), }; #ifdef FDT DRIVER_MODULE(owc_gpiobus, simplebus, owc_gpiobus_driver, owc_gpiobus_devclass, 0, 0); -SIMPLEBUS_PNP_INFO(compat_data); #endif DRIVER_MODULE(owc_gpiobus, gpiobus, owc_gpiobus_driver, owc_gpiobus_devclass, 0, 0); -#ifdef FDT -OFWBUS_PNP_INFO(compat_data); -#endif MODULE_DEPEND(owc_gpiobus, ow, 1, 1, 1); MODULE_DEPEND(owc_gpiobus, gpiobus, 1, 1, 1); MODULE_VERSION(owc_gpiobus, 1); diff --git a/sys/dev/pwm/pwmc.c b/sys/dev/pwm/pwmc.c index b39460edc9c9..c49d1e894488 100644 --- a/sys/dev/pwm/pwmc.c +++ b/sys/dev/pwm/pwmc.c @@ -1,210 +1,210 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018 Emmanuel Vadot * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include "pwmbus_if.h" #ifdef FDT #include #include #include static struct ofw_compat_data compat_data[] = { {"freebsd,pwmc", true}, {NULL, false}, }; + +PWMBUS_FDT_PNP_INFO(compat_data); + #endif struct pwmc_softc { device_t dev; struct cdev *cdev; u_int chan; }; static int pwm_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct pwmc_softc *sc; struct pwm_state state; device_t bus; int rv = 0; sc = dev->si_drv1; bus = device_get_parent(sc->dev); switch (cmd) { case PWMSETSTATE: bcopy(data, &state, sizeof(state)); rv = PWMBUS_CHANNEL_CONFIG(bus, sc->chan, state.period, state.duty); if (rv == 0) rv = PWMBUS_CHANNEL_ENABLE(bus, sc->chan, state.enable); break; case PWMGETSTATE: bcopy(data, &state, sizeof(state)); rv = PWMBUS_CHANNEL_GET_CONFIG(bus, sc->chan, &state.period, &state.duty); if (rv != 0) return (rv); rv = PWMBUS_CHANNEL_IS_ENABLED(bus, sc->chan, &state.enable); if (rv != 0) return (rv); bcopy(&state, data, sizeof(state)); break; } return (rv); } static struct cdevsw pwm_cdevsw = { .d_version = D_VERSION, .d_name = "pwmc", .d_ioctl = pwm_ioctl }; static void pwmc_setup_label(struct pwmc_softc *sc) { const char *hintlabel; #ifdef FDT void *label; if (OF_getprop_alloc(ofw_bus_get_node(sc->dev), "label", &label) > 0) { make_dev_alias(sc->cdev, "pwm/%s", (char *)label); OF_prop_free(label); } #endif if (resource_string_value(device_get_name(sc->dev), device_get_unit(sc->dev), "label", &hintlabel) == 0) { make_dev_alias(sc->cdev, "pwm/%s", hintlabel); } } static int pwmc_probe(device_t dev) { int rv; rv = BUS_PROBE_NOWILDCARD; #ifdef FDT if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { rv = BUS_PROBE_DEFAULT; } #endif device_set_desc(dev, "PWM Control"); return (rv); } static int pwmc_attach(device_t dev) { struct pwmc_softc *sc; struct make_dev_args args; int error; sc = device_get_softc(dev); sc->dev = dev; if ((error = pwmbus_get_channel(dev, &sc->chan)) != 0) return (error); make_dev_args_init(&args); args.mda_flags = MAKEDEV_CHECKNAME | MAKEDEV_WAITOK; args.mda_devsw = &pwm_cdevsw; args.mda_uid = UID_ROOT; args.mda_gid = GID_OPERATOR; args.mda_mode = 0660; args.mda_si_drv1 = sc; error = make_dev_s(&args, &sc->cdev, "pwm/pwmc%d.%d", device_get_unit(device_get_parent(dev)), sc->chan); if (error != 0) { device_printf(dev, "Failed to make PWM device\n"); return (error); } pwmc_setup_label(sc); return (0); } static int pwmc_detach(device_t dev) { struct pwmc_softc *sc; sc = device_get_softc(dev); destroy_dev(sc->cdev); return (0); } static device_method_t pwmc_methods[] = { /* device_if */ DEVMETHOD(device_probe, pwmc_probe), DEVMETHOD(device_attach, pwmc_attach), DEVMETHOD(device_detach, pwmc_detach), DEVMETHOD_END }; static driver_t pwmc_driver = { "pwmc", pwmc_methods, sizeof(struct pwmc_softc), }; static devclass_t pwmc_devclass; DRIVER_MODULE(pwmc, pwmbus, pwmc_driver, pwmc_devclass, 0, 0); -#ifdef FDT -PWMBUS_FDT_PNP_INFO(compat_data); -#endif MODULE_DEPEND(pwmc, pwmbus, 1, 1, 1); MODULE_VERSION(pwmc, 1);