diff --git a/sys/dev/pwm/pwmbus.c b/sys/dev/pwm/pwmbus.c index 7039fde4b07d..2046baade085 100644 --- a/sys/dev/pwm/pwmbus.c +++ b/sys/dev/pwm/pwmbus.c @@ -37,28 +37,131 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include -#include +#include #include "pwmbus_if.h" -struct pwmbus_channel_data { - int reserved; - char *name; -}; - struct pwmbus_softc { device_t dev; - device_t parent; - u_int nchannels; }; +/* + * bus_if methods... + */ + +static device_t +pwmbus_add_child(device_t dev, u_int order, const char *name, int unit) +{ + device_t child; + struct pwmbus_ivars *ivars; + + child = device_add_child_ordered(dev, order, name, unit); + if (child == NULL) + return (child); + + ivars = malloc(sizeof(struct pwmbus_ivars), M_DEVBUF, M_NOWAIT | M_ZERO); + if (ivars == NULL) { + device_delete_child(dev, child); + return (NULL); + } + device_set_ivars(child, ivars); + + return (child); +} + +static int +pwmbus_child_location_str(device_t dev, device_t child, char *buf, size_t blen) +{ + struct pwmbus_ivars *ivars; + + ivars = device_get_ivars(child); + snprintf(buf, blen, "hwdev=%s channel=%u", + device_get_nameunit(device_get_parent(dev)), ivars->pi_channel); + + return (0); +} + +static int +pwmbus_child_pnpinfo_str(device_t dev, device_t child, char *buf, + size_t buflen) +{ + *buf = '\0'; + return (0); +} + +static void +pwmbus_hinted_child(device_t dev, const char *dname, int dunit) +{ + struct pwmbus_ivars *ivars; + device_t child; + + child = pwmbus_add_child(dev, 0, dname, dunit); + + /* + * If there is a channel hint, use it. Otherwise pi_channel was + * initialized to zero, so that's the channel we'll use. + */ + ivars = device_get_ivars(child); + resource_int_value(dname, dunit, "channel", &ivars->pi_channel); +} + +static int +pwmbus_print_child(device_t dev, device_t child) +{ + struct pwmbus_ivars *ivars; + int rv; + + ivars = device_get_ivars(child); + + rv = bus_print_child_header(dev, child); + rv += printf(" channel %u", ivars->pi_channel); + rv += bus_print_child_footer(dev, child); + + return (rv); +} + +static void +pwmbus_probe_nomatch(device_t dev, device_t child) +{ + struct pwmbus_ivars *ivars; + + ivars = device_get_ivars(child); + if (ivars != NULL) + device_printf(dev, " on channel %u\n", + ivars->pi_channel); + + return; +} + +static int +pwmbus_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) +{ + struct pwmbus_ivars *ivars; + + ivars = device_get_ivars(child); + + switch (which) { + case PWMBUS_IVAR_CHANNEL: + *(u_int *)result = ivars->pi_channel; + break; + default: + return (EINVAL); + } + + return (0); +} + +/* + * device_if methods... + */ + static int pwmbus_probe(device_t dev) { - device_set_desc(dev, "PWM bus"); return (BUS_PROBE_GENERIC); } @@ -67,20 +170,33 @@ static int pwmbus_attach(device_t dev) { struct pwmbus_softc *sc; + struct pwmbus_ivars *ivars; + device_t child, parent; + u_int chan; sc = device_get_softc(dev); sc->dev = dev; - sc->parent = device_get_parent(dev); + parent = device_get_parent(dev); - if (PWMBUS_CHANNEL_COUNT(sc->parent, &sc->nchannels) != 0 || + if (PWMBUS_CHANNEL_COUNT(parent, &sc->nchannels) != 0 || sc->nchannels == 0) { device_printf(sc->dev, "No channels on parent %s\n", - device_get_nameunit(sc->parent)); + device_get_nameunit(parent)); return (ENXIO); } - device_add_child(sc->dev, "pwmc", -1); + /* Add a pwmc(4) child for each channel. */ + for (chan = 0; chan < sc->nchannels; ++chan) { + if ((child = pwmbus_add_child(sc->dev, 0, "pwmc", -1)) == NULL) { + device_printf(dev, "failed to add pwmc child device " + "for channel %u\n", chan); + continue; + } + ivars = device_get_ivars(child); + ivars->pi_channel = chan; + } + bus_enumerate_hinted_children(dev); bus_generic_probe(dev); return (bus_generic_attach(dev)); @@ -97,6 +213,10 @@ pwmbus_detach(device_t dev) return (rv); } +/* + * pwmbus_if methods... + */ + static int pwmbus_channel_config(device_t dev, u_int chan, u_int period, u_int duty) { @@ -145,6 +265,15 @@ static device_method_t pwmbus_methods[] = { DEVMETHOD(device_attach, pwmbus_attach), DEVMETHOD(device_detach, pwmbus_detach), + /* bus_if */ + DEVMETHOD(bus_add_child, pwmbus_add_child), + DEVMETHOD(bus_child_location_str, pwmbus_child_location_str), + DEVMETHOD(bus_child_pnpinfo_str, pwmbus_child_pnpinfo_str), + DEVMETHOD(bus_hinted_child, pwmbus_hinted_child), + DEVMETHOD(bus_print_child, pwmbus_print_child), + DEVMETHOD(bus_probe_nomatch, pwmbus_probe_nomatch), + DEVMETHOD(bus_read_ivar, pwmbus_read_ivar), + /* pwmbus_if */ DEVMETHOD(pwmbus_channel_count, pwmbus_channel_count), DEVMETHOD(pwmbus_channel_config, pwmbus_channel_config), @@ -157,13 +286,13 @@ static device_method_t pwmbus_methods[] = { DEVMETHOD_END }; -static driver_t pwmbus_driver = { +driver_t pwmbus_driver = { "pwmbus", pwmbus_methods, sizeof(struct pwmbus_softc), }; -static devclass_t pwmbus_devclass; +devclass_t pwmbus_devclass; EARLY_DRIVER_MODULE(pwmbus, pwm, pwmbus_driver, pwmbus_devclass, 0, 0, - BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_MIDDLE); + BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(pwmbus, 1); diff --git a/sys/dev/pwm/pwmbus.h b/sys/dev/pwm/pwmbus.h new file mode 100644 index 000000000000..ce37ad1ff966 --- /dev/null +++ b/sys/dev/pwm/pwmbus.h @@ -0,0 +1,62 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef _PWMBUS_H_ +#define _PWMBUS_H_ + +struct pwmbus_ivars { + u_int pi_channel; +}; + +enum { + PWMBUS_IVAR_CHANNEL, /* Channel used by child dev */ +}; + +#define PWMBUS_ACCESSOR(A, B, T) \ +static inline int \ +pwmbus_get_ ## A(device_t dev, T *t) \ +{ \ + return BUS_READ_IVAR(device_get_parent(dev), dev, \ + PWMBUS_IVAR_ ## B, (uintptr_t *) t); \ +} \ +static inline int \ +pwmbus_set_ ## A(device_t dev, T t) \ +{ \ + return BUS_WRITE_IVAR(device_get_parent(dev), dev, \ + PWMBUS_IVAR_ ## B, (uintptr_t) t); \ +} + +PWMBUS_ACCESSOR(channel, CHANNEL, u_int) + +extern driver_t pwmbus_driver; +extern devclass_t pwmbus_devclass; +extern driver_t ofw_pwmbus_driver; +extern devclass_t ofw_pwmbus_devclass; + +#endif /* _PWMBUS_H_ */ diff --git a/sys/dev/pwm/pwmc.c b/sys/dev/pwm/pwmc.c index 18b60388a7bc..6b41a51997e4 100644 --- a/sys/dev/pwm/pwmc.c +++ b/sys/dev/pwm/pwmc.c @@ -37,14 +37,15 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include "pwmbus_if.h" struct pwmc_softc { device_t dev; - struct cdev *pwm_dev; - char name[32]; + struct cdev *cdev; + u_int chan; }; static int @@ -68,19 +69,19 @@ pwm_ioctl(struct cdev *dev, u_long cmd, caddr_t data, break; case PWMSETSTATE: bcopy(data, &state, sizeof(state)); - rv = PWMBUS_CHANNEL_CONFIG(bus, state.channel, + rv = PWMBUS_CHANNEL_CONFIG(bus, sc->chan, state.period, state.duty); if (rv == 0) - rv = PWMBUS_CHANNEL_ENABLE(bus, state.channel, + rv = PWMBUS_CHANNEL_ENABLE(bus, sc->chan, state.enable); break; case PWMGETSTATE: bcopy(data, &state, sizeof(state)); - rv = PWMBUS_CHANNEL_GET_CONFIG(bus, state.channel, + rv = PWMBUS_CHANNEL_GET_CONFIG(bus, sc->chan, &state.period, &state.duty); if (rv != 0) return (rv); - rv = PWMBUS_CHANNEL_IS_ENABLED(bus, state.channel, + rv = PWMBUS_CHANNEL_IS_ENABLED(bus, sc->chan, &state.enable); if (rv != 0) return (rv); @@ -93,7 +94,7 @@ pwm_ioctl(struct cdev *dev, u_long cmd, caddr_t data, static struct cdevsw pwm_cdevsw = { .d_version = D_VERSION, - .d_name = "pwm", + .d_name = "pwmc", .d_ioctl = pwm_ioctl }; @@ -101,7 +102,7 @@ static int pwmc_probe(device_t dev) { - device_set_desc(dev, "PWM Controller"); + device_set_desc(dev, "PWM Control"); return (BUS_PROBE_NOWILDCARD); } @@ -110,22 +111,35 @@ pwmc_attach(device_t dev) { struct pwmc_softc *sc; struct make_dev_args args; + const char *label; + int error; sc = device_get_softc(dev); sc->dev = dev; - snprintf(sc->name, sizeof(sc->name), "pwmc%d", device_get_unit(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 = 0600; + args.mda_mode = 0660; args.mda_si_drv1 = sc; - if (make_dev_s(&args, &sc->pwm_dev, "%s", sc->name) != 0) { + error = make_dev_s(&args, &sc->cdev, "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 (ENXIO); + return (error); } + + /* If there is a label hint, create an alias with that name. */ + if (resource_string_value(device_get_name(dev), device_get_unit(dev), + "label", &label) == 0) { + make_dev_alias(sc->cdev, "pwm/%s", label); + } + return (0); } @@ -135,7 +149,7 @@ pwmc_detach(device_t dev) struct pwmc_softc *sc; sc = device_get_softc(dev); - destroy_dev(sc->pwm_dev); + destroy_dev(sc->cdev); return (0); }