commit bcf67581793dcbdfe5716d943a6200ca06866de0 Author: Vladimir Kondratyev Date: Sun Aug 11 15:25:47 2019 +0300 iicbus(4): Add support for ACPI-based children enumeration When iicbus is attached as child of Designware I2C controller it scans all ACPI children for "I2C Serial Bus Connection Resource Descriptor" described in section 19.6.57 of ACPI specs. If such a descriptor is found, I2C child is added to iicbus, its I2C address and ACPI handle are added to bus ivars and all newbus resources including IRQ are copied from ACPI-hosted device to iicbus-hosted one. This effectively makes a copy of existing ACPI device on top of I2C bus. The dummy acpi_iicdev driver is attached to ACPI child device to block possible attachment of other drivers. diff --git a/sys/conf/files b/sys/conf/files index 50b111a0d16b..668cfbda7d9c 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1766,6 +1766,7 @@ dev/ichsmb/ichsmb_pci.c optional ichsmb pci dev/ida/ida.c optional ida dev/ida/ida_disk.c optional ida dev/ida/ida_pci.c optional ida pci +dev/iicbus/acpi_iicbus.c optional acpi iicbus dev/iicbus/ad7418.c optional ad7418 dev/iicbus/ads111x.c optional ads111x dev/iicbus/ds1307.c optional ds1307 diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c index c6b0796de478..3ba435b00d8f 100644 --- a/sys/dev/acpica/acpi.c +++ b/sys/dev/acpica/acpi.c @@ -161,7 +161,6 @@ static ACPI_STATUS acpi_sleep_disable(struct acpi_softc *sc); static ACPI_STATUS acpi_EnterSleepState(struct acpi_softc *sc, int state); static void acpi_shutdown_final(void *arg, int howto); static void acpi_enable_fixed_events(struct acpi_softc *sc); -static BOOLEAN acpi_has_hid(ACPI_HANDLE handle); static void acpi_resync_clock(struct acpi_softc *sc); static int acpi_wake_sleep_prep(ACPI_HANDLE handle, int sstate); static int acpi_wake_run_prep(ACPI_HANDLE handle, int sstate); @@ -2261,7 +2260,7 @@ acpi_BatteryIsPresent(device_t dev) /* * Returns true if a device has at least one valid device ID. */ -static BOOLEAN +BOOLEAN acpi_has_hid(ACPI_HANDLE h) { ACPI_DEVICE_INFO *devinfo; diff --git a/sys/dev/acpica/acpivar.h b/sys/dev/acpica/acpivar.h index ec1cee6fb623..ca389808b0d7 100644 --- a/sys/dev/acpica/acpivar.h +++ b/sys/dev/acpica/acpivar.h @@ -371,6 +371,7 @@ int acpi_bus_alloc_gas(device_t dev, int *type, int *rid, u_int flags); void acpi_walk_subtables(void *first, void *end, acpi_subtable_handler *handler, void *arg); +BOOLEAN acpi_has_hid(ACPI_HANDLE handle); int acpi_MatchHid(ACPI_HANDLE h, const char *hid); #define ACPI_MATCHHID_NOMATCH 0 #define ACPI_MATCHHID_HID 1 diff --git a/sys/dev/ichiic/ig4_acpi.c b/sys/dev/ichiic/ig4_acpi.c index f0b431fd1007..52b333bd57a2 100644 --- a/sys/dev/ichiic/ig4_acpi.c +++ b/sys/dev/ichiic/ig4_acpi.c @@ -154,6 +154,17 @@ static device_method_t ig4iic_acpi_methods[] = { DEVMETHOD(device_attach, ig4iic_acpi_attach), DEVMETHOD(device_detach, ig4iic_acpi_detach), + /* Bus interface */ + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), + DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), + DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), + /* iicbus interface */ DEVMETHOD(iicbus_transfer, ig4iic_transfer), DEVMETHOD(iicbus_reset, ig4iic_reset), @@ -163,7 +174,7 @@ static device_method_t ig4iic_acpi_methods[] = { }; static driver_t ig4iic_acpi_driver = { - "ig4iic_acpi", + "ig4iic", ig4iic_acpi_methods, sizeof(struct ig4iic_softc), }; diff --git a/sys/dev/ichiic/ig4_pci.c b/sys/dev/ichiic/ig4_pci.c index 8d7275ae57e6..fa08dc57013a 100644 --- a/sys/dev/ichiic/ig4_pci.c +++ b/sys/dev/ichiic/ig4_pci.c @@ -212,6 +212,18 @@ static device_method_t ig4iic_pci_methods[] = { DEVMETHOD(device_attach, ig4iic_pci_attach), DEVMETHOD(device_detach, ig4iic_pci_detach), + /* Bus interface */ + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), + DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), + DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), + + /* iicbus interface */ DEVMETHOD(iicbus_transfer, ig4iic_transfer), DEVMETHOD(iicbus_reset, ig4iic_reset), DEVMETHOD(iicbus_callback, iicbus_null_callback), @@ -220,7 +232,7 @@ static device_method_t ig4iic_pci_methods[] = { }; static driver_t ig4iic_pci_driver = { - "ig4iic_pci", + "ig4iic", ig4iic_pci_methods, sizeof(struct ig4iic_softc) }; diff --git a/sys/dev/iicbus/acpi_iicbus.c b/sys/dev/iicbus/acpi_iicbus.c new file mode 100644 index 000000000000..13a138abd086 --- /dev/null +++ b/sys/dev/iicbus/acpi_iicbus.c @@ -0,0 +1,429 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Vladimir Kondratyev + * + * 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 "iicbus.h" + +struct acpi_iicbus_ivar { + device_t dev; + ACPI_HANDLE handle; + STAILQ_ENTRY(acpi_iicbus_ivar) link; +}; + +struct acpi_iicbus_softc { + struct iicbus_softc isc; + STAILQ_HEAD(, acpi_iicbus_ivar) ivars; +}; + + +static ACPI_STATUS acpi_iicdev_get_i2cres(ACPI_HANDLE handle, + struct acpi_resource_i2c_serialbus *i2cres); + + +static int +acpi_iicbus_copy_newbus_resources(device_t from, device_t to) +{ + struct resource_list_entry *from_rle; + struct resource_list *from_rl; + + /* Loop through all current resources and copy them to the target. */ + from_rl = BUS_GET_RESOURCE_LIST(device_get_parent(from), from); + STAILQ_FOREACH(from_rle, from_rl, link) { + + bus_set_resource(to, from_rle->type, from_rle->rid, + from_rle->start, from_rle->count); + } + + return (0); +} + +static void +acpi_iicbus_dump_res(device_t dev, struct acpi_resource_i2c_serialbus *res) +{ + device_printf(dev, "found ACPI child\n"); + printf(" SlaveAddress: 0x%04hx\n", res->SlaveAddress); + printf(" ConnectionSpeed: %uHz\n", res->ConnectionSpeed); + printf(" SlaveMode: %s\n", + res->SlaveMode == ACPI_CONTROLLER_INITIATED ? + "ControllerInitiated" : "DeviceInitiated"); + printf(" AddressingMode: %uBit\n", res->AccessMode == 0 ? 7 : 10); + printf(" ConnectionSharing: %s\n", res->ConnectionSharing == 0 ? + "Exclusive" : "Shared"); +} + +static ACPI_STATUS +acpi_iicbus_enumerate_children_cb(ACPI_HANDLE handle, UINT32 level, + void *context, void **status) +{ + devclass_t acpi_iicdev_devclass; + device_t iicbus = context; + device_t child, acpi_iicdev; + struct acpi_resource_i2c_serialbus res; + struct acpi_iicbus_ivar *ivar; + struct acpi_iicbus_softc *sc = device_get_softc(iicbus); + UINT32 sta; + + /* + * If no _STA method or if it failed, then assume that + * the device is present. + */ + if (!ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) && + !ACPI_DEVICE_PRESENT(sta)) + return (AE_OK); + + if (!acpi_has_hid(handle)) + return (AE_OK); + + /* + * Read "I2C Serial Bus Connection Resource Descriptor" + * described in p.19.6.57 of ACPI specification. + */ + bzero(&res, sizeof(struct acpi_resource_i2c_serialbus)); + if (ACPI_FAILURE(acpi_iicdev_get_i2cres(handle, &res)) || + res.SlaveAddress == 0) + return (AE_OK); + + /* + * Ensure dummy driver is attached. We are going to use resources from + * the ACPI device so don't let other drivers occupy its place. + */ + acpi_iicdev = acpi_get_device(handle); + if (acpi_iicdev == NULL) + return (AE_OK); + + if (!device_is_alive(acpi_iicdev)) + device_probe_and_attach(acpi_iicdev); + + acpi_iicdev_devclass = devclass_find("acpi_iicdev"); + if (device_get_devclass(acpi_iicdev) != acpi_iicdev_devclass) + return (AE_OK); + + /* Unused ACPI I2C device found. Add a child to I2C bus */ + if (bootverbose) + acpi_iicbus_dump_res(iicbus, &res); + + child = BUS_ADD_CHILD(iicbus, 0, NULL, -1); + if (child == NULL) { + device_printf(iicbus, "add child failed\n"); + return (AE_OK); + } + + iicbus_set_addr(child, res.SlaveAddress); + + /* Now set up the I2C to ACPI bus mapping and add it to the ivars */ + ivar = malloc(sizeof(*ivar), M_DEVBUF, M_WAITOK | M_ZERO); + ivar->dev = child; + ivar->handle = handle; + STAILQ_INSERT_TAIL(&sc->ivars, ivar, link); + + /* Copy all resources including IRQ from ACPI to I2C device */ + acpi_iicbus_copy_newbus_resources(acpi_iicdev, child); + + device_probe_and_attach(child); + + return (AE_OK); +} + +static ACPI_STATUS +acpi_iicbus_enumerate_children(device_t dev, ACPI_HANDLE handle) +{ + ACPI_STATUS status; + + status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, + 1, acpi_iicbus_enumerate_children_cb, NULL, dev, NULL); + + return (status); +} + +static int +acpi_iicbus_probe(device_t dev) +{ + ACPI_HANDLE handle; + device_t controller; + + controller = device_get_parent(dev); + if (controller == NULL) + return (ENXIO); + + handle = acpi_get_handle(controller); + if (handle == NULL) + return (ENXIO); + + device_set_desc(dev, "Philips I2C bus (ACPI-hinted)"); + + return (BUS_PROBE_DEFAULT); +} + +static int +acpi_iicbus_attach(device_t dev) +{ + struct acpi_iicbus_softc *sc = device_get_softc(dev); + ACPI_HANDLE handle; + int error; + + handle = acpi_get_handle(device_get_parent(dev)); + if (handle == NULL) + return (ENXIO); + + error = iicbus_attach(dev); + if (error) + return (error); + + STAILQ_INIT(&sc->ivars); + + /* Walk through ACPI children of I2C controller */ + if (ACPI_FAILURE(acpi_iicbus_enumerate_children(dev, handle))) + device_printf(dev, "children enumeration failed\n"); + + return (0); +} + +static int +acpi_iicbus_detach(device_t dev) +{ + struct acpi_iicbus_softc *sc = device_get_softc(dev); + struct acpi_iicbus_ivar *ivar; + int error; + + while (!STAILQ_EMPTY(&sc->ivars)) { + ivar = STAILQ_FIRST(&sc->ivars); + error = device_detach(ivar->dev); + if (error) + return (error); + STAILQ_REMOVE_HEAD(&sc->ivars, link); + device_delete_child(dev, ivar->dev); + free(ivar, M_DEVBUF); + } + + return (iicbus_detach(dev)); +} + +static int +acpi_iicbus_read_ivar(device_t dev, device_t child, int idx, + uintptr_t *res) +{ + struct acpi_iicbus_softc *sc = device_get_softc(dev); + struct acpi_iicbus_ivar *ivar; + + if (idx == ACPI_IVAR_HANDLE) { + STAILQ_FOREACH(ivar, &sc->ivars, link) { + if (ivar->dev == child) { + *res = (uintptr_t)ivar->handle; + return (0); + } + } + return (ENXIO); + } + + return (iicbus_read_ivar(dev, child, idx, res)); +} + +/* Location hint for devctl(8) */ +static int +acpi_iicbus_child_location_str(device_t bus, device_t child, + char *buf, size_t buflen) +{ + ACPI_HANDLE handle; + + iicbus_child_location_str(bus, child, buf, buflen); + handle = acpi_get_handle(child); + if (handle) { + strlcat(buf, " handle=", buflen); + strlcat(buf, acpi_name(handle), buflen); + } + return (0); +} + +/* PnP information for devctl(8) */ +static int +acpi_iicbus_child_pnpinfo_str(device_t bus, device_t child, char *buf, + size_t buflen) +{ + ACPI_HANDLE handle; + ACPI_DEVICE_INFO *devinfo; + size_t len; + + iicbus_child_pnpinfo_str(bus, child, buf, buflen); + + /* Paranoic check as parent method always return 0-length string */ + len = strnlen(buf, buflen); + if (len + 2 >= buflen) + return (0); + + handle = acpi_get_handle(child); + if (handle && ACPI_SUCCESS(AcpiGetObjectInfo(handle, &devinfo))) { + + snprintf(buf + len, buflen - len, "%s_HID=%s _UID=%lu", + len == 0 ? "" : " ", + (devinfo->Valid & ACPI_VALID_HID) ? + devinfo->HardwareId.String : "none", + (devinfo->Valid & ACPI_VALID_UID) ? + strtoul(devinfo->UniqueId.String, NULL, 10) : 0UL); + + AcpiOsFree(devinfo); + } + + return (0); +} + +static device_method_t acpi_iicbus_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_iicbus_probe), + DEVMETHOD(device_attach, acpi_iicbus_attach), + DEVMETHOD(device_detach, acpi_iicbus_detach), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, acpi_iicbus_read_ivar), + DEVMETHOD(bus_child_location_str, acpi_iicbus_child_location_str), + DEVMETHOD(bus_child_pnpinfo_str, acpi_iicbus_child_pnpinfo_str), + + DEVMETHOD_END, +}; + +static kobj_class_t iicbus_baseclasses[] = { &iicbus_driver, NULL }; +static driver_t acpi_iicbus_driver = { + .name = "iicbus", + .methods = acpi_iicbus_methods, + .size = sizeof(struct acpi_iicbus_softc), + .baseclasses = iicbus_baseclasses, +}; + +MODULE_VERSION(acpi_iicbus, 1); +MODULE_DEPEND(acpi_iicbus, acpi, 1, 1, 1); +DRIVER_MODULE(acpi_iicbus, ig4iic, acpi_iicbus_driver, iicbus_devclass, 0, 0); + +/* + * Dummy ACPI driver. Used as bus resource holder for iicbus children. + */ + +static ACPI_STATUS +acpi_iicdev_get_i2cres_cb(ACPI_RESOURCE *res, void *context) +{ + + if (res->Type == ACPI_RESOURCE_TYPE_SERIAL_BUS && + res->Data.CommonSerialBus.Type == ACPI_RESOURCE_SERIAL_TYPE_I2C) { + if (context != NULL) + memcpy(context, &res->Data.I2cSerialBus, + sizeof(struct acpi_resource_i2c_serialbus)); + return (AE_CTRL_TERMINATE); + } else if (res->Type == ACPI_RESOURCE_TYPE_END_TAG) + return (AE_NOT_FOUND); + + return (AE_OK); +} + +static ACPI_STATUS +acpi_iicdev_get_i2cres(ACPI_HANDLE handle, + struct acpi_resource_i2c_serialbus *i2cres) +{ + ACPI_STATUS status; + + status = AcpiWalkResources(handle, "_CRS", + acpi_iicdev_get_i2cres_cb, i2cres); + + return (status); +} + +static int +acpi_iicdev_probe(device_t dev) +{ + ACPI_HANDLE handle, phandle; + device_t controller; + + if (acpi_disabled("iic")) + return (ENXIO); + + /* Check if device have I2C address and PNP info */ + handle = acpi_get_handle(dev); + if (handle == NULL || !acpi_has_hid(handle) || + ACPI_FAILURE(acpi_iicdev_get_i2cres(handle, NULL))) + return (ENXIO); + + /* Device should be a child of I2C controller */ + if (ACPI_FAILURE(AcpiGetParent(handle, &phandle)) || + (controller = acpi_get_device(phandle)) == NULL || + device_get_devclass(controller) != devclass_find("ig4iic")) + return (ENXIO); + + device_set_desc(dev, "ACPI Generic I2C Device"); + device_quiet(dev); + + return (BUS_PROBE_GENERIC); +} + +static int +acpi_iicdev_attach(device_t dev) +{ + + return (0); +} + +static int +acpi_iicdev_detach(device_t dev) +{ + + return (0); +} + +static devclass_t acpi_iicdev_devclass; + +static device_method_t acpi_iicdev_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_iicdev_probe), + DEVMETHOD(device_attach, acpi_iicdev_attach), + DEVMETHOD(device_detach, acpi_iicdev_detach), + + DEVMETHOD_END +}; + +static driver_t acpi_iicdev_driver = { + .name = "acpi_iicdev", + .methods = acpi_iicdev_methods, + .size = 0, +}; + +DRIVER_MODULE(acpi_iicdev, acpi, acpi_iicdev_driver, acpi_iicdev_devclass, + NULL, 0); +MODULE_DEPEND(acpi_iicdev, acpi, 1, 1, 1); +MODULE_VERSION(acpi_iicdev, 1); diff --git a/sys/dev/iicbus/iicbus.c b/sys/dev/iicbus/iicbus.c index 0795b99dcd69..a4417bc36252 100644 --- a/sys/dev/iicbus/iicbus.c +++ b/sys/dev/iicbus/iicbus.c @@ -89,7 +89,7 @@ iic_probe_device(device_t dev, u_char addr) * We add all the devices which we know about. * The generic attach routine will attach them if they are alive. */ -static int +int iicbus_attach(device_t dev) { #if SCAN_IICBUS @@ -130,7 +130,7 @@ iicbus_attach(device_t dev) return (0); } -static int +int iicbus_detach(device_t dev) { struct iicbus_softc *sc = IICBUS_SOFTC(dev); @@ -165,7 +165,7 @@ iicbus_probe_nomatch(device_t bus, device_t child) device_printf(bus, " at addr %#x\n", devi->addr); } -static int +int iicbus_child_location_str(device_t bus, device_t child, char *buf, size_t buflen) { @@ -175,7 +175,7 @@ iicbus_child_location_str(device_t bus, device_t child, char *buf, return (0); } -static int +int iicbus_child_pnpinfo_str(device_t bus, device_t child, char *buf, size_t buflen) { @@ -183,7 +183,7 @@ iicbus_child_pnpinfo_str(device_t bus, device_t child, char *buf, return (0); } -static int +int iicbus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct iicbus_ivar *devi = IICBUS_IVAR(child); diff --git a/sys/dev/iicbus/iicbus.h b/sys/dev/iicbus/iicbus.h index c6382b63e8d0..9b6b0426286d 100644 --- a/sys/dev/iicbus/iicbus.h +++ b/sys/dev/iicbus/iicbus.h @@ -79,6 +79,15 @@ IICBUS_ACCESSOR(addr, ADDR, uint32_t) int iicbus_generic_intr(device_t dev, int event, char *buf); void iicbus_init_frequency(device_t dev, u_int bus_freq); +int iicbus_attach(device_t dev); +int iicbus_detach(device_t dev); +int iicbus_read_ivar(device_t bus, device_t child, int which, + uintptr_t *result); +int iicbus_child_location_str(device_t bus, device_t child, char *buf, + size_t buflen); +int iicbus_child_pnpinfo_str(device_t bus, device_t child, char *buf, + size_t buflen); + extern driver_t iicbus_driver; extern devclass_t iicbus_devclass; extern driver_t ofw_iicbus_driver; diff --git a/sys/modules/i2c/iicbus/Makefile b/sys/modules/i2c/iicbus/Makefile index 025557163be6..8923615ecada 100644 --- a/sys/modules/i2c/iicbus/Makefile +++ b/sys/modules/i2c/iicbus/Makefile @@ -15,6 +15,11 @@ SRCS= \ iiconf.h \ opt_platform.h \ +.if ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \ + ${MACHINE_CPUARCH} == "i386" +SRCS+= acpi_iicbus.c opt_acpi.h acpi_if.h +.endif + .if !empty(OPT_FDT) SRCS+= ofw_iicbus.c ofw_bus_if.h .endif