Index: sys/conf/files =================================================================== --- sys/conf/files +++ sys/conf/files @@ -1780,6 +1780,7 @@ 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 Index: sys/dev/iicbus/acpi_iicbus.c =================================================================== --- /dev/null +++ sys/dev/iicbus/acpi_iicbus.c @@ -0,0 +1,451 @@ +/*- + * 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 + +/* Hooks for the ACPI CA debugging infrastructure. */ +#define _COMPONENT ACPI_BUS +ACPI_MODULE_NAME("IIC") + +static devclass_t acpi_iicdev_devclass; + +struct acpi_iicbus_ivars { + struct iicbus_ivar ivar; + ACPI_HANDLE handle; +}; + +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_iicbus_copy_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 device_t +acpi_iicbus_add_child(device_t dev, u_int order, const char *name, int unit) +{ + + return (iicbus_add_child_common( + dev, order, name, unit, sizeof(struct acpi_iicbus_ivars))); +} + +static ACPI_STATUS +acpi_iicbus_enumerate_children_cb(ACPI_HANDLE handle, UINT32 level, + void *context, void **status) +{ + device_t iicbus = context; + device_t child, acpi_iicdev, acpi0; + struct acpi_resource_i2c_serialbus res; + struct iicbus_softc *sc = device_get_softc(iicbus); + + acpi_iicdev = acpi_get_device(handle); + if (acpi_iicdev == NULL) + return (AE_OK); + + /* Reuse device presence check result made by ACPI via _STA method */ + if (!device_is_enabled(acpi_iicdev)) + return (AE_OK); + + /* Check that device have a _HID/_CID i.e. it is a child of acpi bus */ + acpi0 = devclass_get_device(devclass_find("acpi"), 0); + if (device_get_parent(acpi_iicdev) != acpi0) + 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); + if (bootverbose) + acpi_iicbus_dump_res(iicbus, &res); + + /* Find out speed of the slowest slave */ + if (sc->bus_freq == 0 || sc->bus_freq > res.ConnectionSpeed) + sc->bus_freq = res.ConnectionSpeed; + + /* + * Ensure dummy driver is attached. We are going to use resources from + * the ACPI device so don't let other drivers occupy his place. + */ + if (device_get_devclass(acpi_iicdev) != acpi_iicdev_devclass && + device_set_devclass_fixed(acpi_iicdev, "acpi_iicdev") != 0) { + device_printf(iicbus, "Can't set ACPI child %s devclass. " + "Ignore it.\n", device_get_nameunit(acpi_iicdev)); + return (AE_OK); + } + if (device_probe_and_attach(acpi_iicdev) != 0) { + device_printf(iicbus, "failed to attach dummy driver\n"); + return (AE_OK); + } + + /* Appropriate ACPI I2C device found. Add a child to I2C bus */ + 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); + acpi_set_handle(child, handle); + + /* Copy all resources including IRQs from ACPI to I2C device */ + acpi_iicbus_copy_resources(acpi_iicdev, 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; + + if (acpi_disabled("iicbus")) + return (ENXIO); + + controller = device_get_parent(dev); + if (controller == NULL) + return (ENXIO); + + handle = acpi_get_handle(controller); + if (handle == NULL) + return (ENXIO); + + return (BUS_PROBE_DEFAULT); +} + +static int +acpi_iicbus_attach(device_t dev) +{ + struct iicbus_softc *sc = device_get_softc(dev); + ACPI_HANDLE handle; + + device_set_desc(dev, "Philips I2C bus (ACPI-hinted)"); + + /* Walk through ACPI children of I2C controller */ + handle = acpi_get_handle(device_get_parent(dev)); + if (ACPI_FAILURE(acpi_iicbus_enumerate_children(dev, handle))) + device_printf(dev, "children enumeration failed\n"); + + return (iicbus_attach_common(dev, sc->bus_freq)); +} + +static int +acpi_iicbus_read_ivar(device_t bus, device_t child, int which, uintptr_t *res) +{ + struct acpi_iicbus_ivars *devi = device_get_ivars(child); + + switch (which) { + case ACPI_IVAR_HANDLE: + *res = (uintptr_t)devi->handle; + break; + default: + return (iicbus_read_ivar(bus, child, which, res)); + } + + return (0); +} + +static int +acpi_iicbus_write_ivar(device_t bus, device_t child, int which, uintptr_t val) +{ + struct acpi_iicbus_ivars *devi = device_get_ivars(child); + + switch (which) { + case ACPI_IVAR_HANDLE: + if (devi->handle != NULL) + return (EINVAL); + devi->handle = (ACPI_HANDLE)val; + break; + default: + return (iicbus_write_ivar(bus, child, which, val)); + } + return (0); +} + +/* Finds ACPI-hosted device corresponding IIC-hosted one */ +static device_t +acpi_iicbus_get_acpi_dev(device_t iic_dev) +{ + ACPI_HANDLE handle; + device_t acpi_dev, acpi0; + + handle = acpi_get_handle(iic_dev); + if (handle == NULL) + return (NULL); + + acpi_dev = acpi_get_device(handle); + if (acpi_dev == NULL) + return (NULL); + + /* Check that device is a child of acpi bus */ + acpi0 = devclass_get_device(devclass_find("acpi"), 0); + if (device_get_parent(acpi_dev) != acpi0) + return (NULL); + + return (acpi_dev); +} + +/* Location hint for devctl(8). Concatenate IIC and ACPI hints */ +static int +acpi_iicbus_child_location_str(device_t bus, device_t child, + char *buf, size_t buflen) +{ + device_t acpi_dev; + size_t acpi_offset; + int error; + + /* read IIC location hint string into the buffer */ + error = iicbus_child_location_str(bus, child, buf, buflen); + if (error) + return (error); + + acpi_dev = acpi_iicbus_get_acpi_dev(child); + if (acpi_dev == NULL) + return (0); + + if (buf[0] != '\0') + strlcat(buf, " ", buflen); + strlcat(buf, "acpi_iicdev=", buflen); + strlcat(buf, device_get_nameunit(acpi_dev), buflen); + + /* Neither IIC nor ACPI children return EOVERFLOW. So we too */ + acpi_offset = strnlen(buf, buflen) + 1; + if (acpi_offset >= buflen) + return (0); + + /* Place ACPI string right after IIC one's terminating NUL */ + error = BUS_CHILD_LOCATION_STR(device_get_parent(acpi_dev), acpi_dev, + buf + acpi_offset, buflen - acpi_offset); + if (error) + return (error); + + /* Coalesce both strings if they are not empty */ + if (buf[acpi_offset] != '\0') + buf[acpi_offset - 1] = ' '; + + return (0); +} + +/* PnP information for devctl(8). Concatenate IIC and ACPI info strings */ +static int +acpi_iicbus_child_pnpinfo_str(device_t bus, device_t child, char *buf, + size_t buflen) +{ + device_t acpi_dev; + size_t acpi_offset; + int error; + + /* read IIC PnP string into the buffer */ + error = iicbus_child_pnpinfo_str(bus, child, buf, buflen); + if (error) + return (error); + + acpi_dev = acpi_iicbus_get_acpi_dev(child); + if (acpi_dev == NULL) + return (0); + + /* Neither IIC nor ACPI children return EOVERFLOW. So we too */ + acpi_offset = strnlen(buf, buflen); + if (acpi_offset != 0) + acpi_offset++; + if (acpi_offset >= buflen) + return (0); + + /* Place ACPI string right after IIC one's terminating NUL */ + error = BUS_CHILD_PNPINFO_STR(device_get_parent(acpi_dev), acpi_dev, + buf + acpi_offset, buflen - acpi_offset); + if (error) + return (error); + + /* Coalesce both strings if they are not empty */ + if (acpi_offset != 0 && buf[acpi_offset] != '\0') + buf[acpi_offset - 1] = ' '; + + return (0); +} + +static device_method_t acpi_iicbus_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_iicbus_probe), + DEVMETHOD(device_attach, acpi_iicbus_attach), + + /* Bus interface */ + DEVMETHOD(bus_add_child, acpi_iicbus_add_child), + DEVMETHOD(bus_read_ivar, acpi_iicbus_read_ivar), + DEVMETHOD(bus_write_ivar, acpi_iicbus_write_ivar), + DEVMETHOD(bus_child_location_str,acpi_iicbus_child_location_str), + DEVMETHOD(bus_child_pnpinfo_str,acpi_iicbus_child_pnpinfo_str), + + DEVMETHOD_END, +}; + +DEFINE_CLASS_1(iicbus, acpi_iicbus_driver, acpi_iicbus_methods, + sizeof(struct iicbus_softc), iicbus_driver); +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. Also, + * being attached it triggers _PS0/3 ACPI methods execution on suspend/resume. + */ + +static int +acpi_iicdev_probe(device_t dev) +{ + ACPI_HANDLE handle; + + /* Check if device has both fixed devclass and I2C address */ + if (!device_is_devclass_fixed(dev) || + (handle = acpi_get_handle(dev)) == NULL || + ACPI_FAILURE(acpi_iicdev_get_i2cres(handle, NULL))) + 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 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); Index: sys/dev/iicbus/iicbus.h =================================================================== --- sys/dev/iicbus/iicbus.h +++ sys/dev/iicbus/iicbus.h @@ -57,6 +57,7 @@ struct resource_list rl; }; +/* Value of 0x100 is reserverd for ACPI_IVAR_HANDLE used by acpi_iicbus */ enum { IICBUS_IVAR_ADDR /* Address or base address */ }; @@ -79,6 +80,19 @@ 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_common(device_t dev, u_int bus_freq); +device_t iicbus_add_child_common(device_t dev, u_int order, const char *name, + int unit, size_t ivars_size); + +int iicbus_read_ivar(device_t bus, device_t child, int which, + uintptr_t *result); +int iicbus_write_ivar(device_t bus, device_t child, int which, + uintptr_t value); +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; Index: sys/dev/iicbus/iicbus.c =================================================================== --- sys/dev/iicbus/iicbus.c +++ sys/dev/iicbus/iicbus.c @@ -89,8 +89,8 @@ * We add all the devices which we know about. * The generic attach routine will attach them if they are alive. */ -static int -iicbus_attach(device_t dev) +int +iicbus_attach_common(device_t dev, u_int bus_freq) { #if SCAN_IICBUS unsigned char addr; @@ -100,7 +100,7 @@ sc->dev = dev; mtx_init(&sc->lock, "iicbus", NULL, MTX_DEF); - iicbus_init_frequency(dev, 0); + iicbus_init_frequency(dev, bus_freq); iicbus_reset(dev, IIC_FASTEST, 0, NULL); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "strict", &strict) == 0) @@ -130,6 +130,13 @@ return (0); } +static int +iicbus_attach(device_t dev) +{ + + return(iicbus_attach_common(dev, 0)); +} + static int iicbus_detach(device_t dev) { @@ -166,7 +173,7 @@ 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) { @@ -176,7 +183,7 @@ return (0); } -static int +int iicbus_child_pnpinfo_str(device_t bus, device_t child, char *buf, size_t buflen) { @@ -184,7 +191,7 @@ 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); @@ -199,7 +206,7 @@ return (0); } -static int +int iicbus_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct iicbus_ivar *devi = IICBUS_IVAR(child); @@ -215,8 +222,9 @@ return (0); } -static device_t -iicbus_add_child(device_t dev, u_int order, const char *name, int unit) +device_t +iicbus_add_child_common(device_t dev, u_int order, const char *name, int unit, + size_t ivars_size) { device_t child; struct iicbus_ivar *devi; @@ -224,7 +232,7 @@ child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (child); - devi = malloc(sizeof(struct iicbus_ivar), M_DEVBUF, M_NOWAIT | M_ZERO); + devi = malloc(ivars_size, M_DEVBUF, M_NOWAIT | M_ZERO); if (devi == NULL) { device_delete_child(dev, child); return (0); @@ -234,6 +242,14 @@ return (child); } +static device_t +iicbus_add_child(device_t dev, u_int order, const char *name, int unit) +{ + + return (iicbus_add_child_common( + dev, order, name, unit, sizeof(struct iicbus_ivar))); +} + static void iicbus_hinted_child(device_t bus, const char *dname, int dunit) { Index: sys/modules/i2c/iicbus/Makefile =================================================================== --- sys/modules/i2c/iicbus/Makefile +++ sys/modules/i2c/iicbus/Makefile @@ -15,6 +15,11 @@ 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