Index: head/sys/conf/files =================================================================== --- head/sys/conf/files +++ head/sys/conf/files @@ -1773,6 +1773,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: head/sys/dev/ichiic/ig4_iic.c =================================================================== --- head/sys/dev/ichiic/ig4_iic.c +++ head/sys/dev/ichiic/ig4_iic.c @@ -1201,5 +1201,9 @@ devclass_t ig4iic_devclass; DRIVER_MODULE(iicbus, ig4iic, iicbus_driver, iicbus_devclass, NULL, NULL); +#ifdef DEV_ACPI +DRIVER_MODULE(acpi_iicbus, ig4iic, acpi_iicbus_driver, iicbus_devclass, NULL, + NULL); +#endif MODULE_DEPEND(ig4iic, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_VERSION(ig4iic, 1); Index: head/sys/dev/iicbus/acpi_iicbus.c =================================================================== --- head/sys/dev/iicbus/acpi_iicbus.c +++ head/sys/dev/iicbus/acpi_iicbus.c @@ -0,0 +1,789 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019-2020 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 +#include + +#define ACPI_IICBUS_LOCAL_BUFSIZE 32 /* Fits max SMBUS block size */ + +/* + * Make a copy of ACPI_RESOURCE_I2C_SERIALBUS type and replace "pointer to ACPI + * object name string" field with pointer to ACPI object itself. + * This saves us extra strdup()/free() pair on acpi_iicbus_get_i2cres call. + */ +typedef ACPI_RESOURCE_I2C_SERIALBUS ACPI_IICBUS_RESOURCE_I2C_SERIALBUS; +#define ResourceSource_Handle ResourceSource.StringPtr + +/* Hooks for the ACPI CA debugging infrastructure. */ +#define _COMPONENT ACPI_BUS +ACPI_MODULE_NAME("IIC") + +struct gsb_buffer { + UINT8 status; + UINT8 len; + UINT8 data[]; +} __packed; + +struct acpi_iicbus_softc { + struct iicbus_softc super_sc; + ACPI_CONNECTION_INFO space_handler_info; + bool space_handler_installed; +}; + +struct acpi_iicbus_ivars { + struct iicbus_ivar super_ivar; + ACPI_HANDLE handle; +}; + +static int install_space_handler = 0; +TUNABLE_INT("hw.iicbus.enable_acpi_space_handler", &install_space_handler); + +static inline bool +acpi_resource_is_i2c_serialbus(ACPI_RESOURCE *res) +{ + + return (res->Type == ACPI_RESOURCE_TYPE_SERIAL_BUS && + res->Data.CommonSerialBus.Type == ACPI_RESOURCE_SERIAL_TYPE_I2C); +} + +/* + * IICBUS Address space handler + */ +static int +acpi_iicbus_sendb(device_t dev, u_char slave, char byte) +{ + struct iic_msg msgs[] = { + { slave, IIC_M_WR, 1, &byte }, + }; + + return (iicbus_transfer(dev, msgs, nitems(msgs))); +} + +static int +acpi_iicbus_recvb(device_t dev, u_char slave, char *byte) +{ + char buf; + struct iic_msg msgs[] = { + { slave, IIC_M_RD, 1, &buf }, + }; + int error; + + error = iicbus_transfer(dev, msgs, nitems(msgs)); + if (error == 0) + *byte = buf; + + return (error); +} + +static int +acpi_iicbus_write(device_t dev, u_char slave, char cmd, void *buf, + uint16_t buflen) +{ + struct iic_msg msgs[] = { + { slave, IIC_M_WR | IIC_M_NOSTOP, 1, &cmd }, + { slave, IIC_M_WR | IIC_M_NOSTART, buflen, buf }, + }; + + return (iicbus_transfer(dev, msgs, nitems(msgs))); +} + +static int +acpi_iicbus_read(device_t dev, u_char slave, char cmd, void *buf, + uint16_t buflen) +{ + uint8_t local_buffer[ACPI_IICBUS_LOCAL_BUFSIZE]; + struct iic_msg msgs[] = { + { slave, IIC_M_WR | IIC_M_NOSTOP, 1, &cmd }, + { slave, IIC_M_RD, buflen, NULL }, + }; + int error; + + if (buflen <= sizeof(local_buffer)) + msgs[1].buf = local_buffer; + else + msgs[1].buf = malloc(buflen, M_DEVBUF, M_WAITOK); + error = iicbus_transfer(dev, msgs, nitems(msgs)); + if (error == 0) + memcpy(buf, msgs[1].buf, buflen); + if (msgs[1].buf != local_buffer) + free(msgs[1].buf, M_DEVBUF); + + return (error); +} + +static int +acpi_iicbus_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf) +{ + uint8_t bytes[2] = { cmd, count }; + struct iic_msg msgs[] = { + { slave, IIC_M_WR | IIC_M_NOSTOP, nitems(bytes), bytes }, + { slave, IIC_M_WR | IIC_M_NOSTART, count, buf }, + }; + + if (count == 0) + return (errno2iic(EINVAL)); + + return (iicbus_transfer(dev, msgs, nitems(msgs))); +} + +static int +acpi_iicbus_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf) +{ + uint8_t local_buffer[ACPI_IICBUS_LOCAL_BUFSIZE]; + u_char len; + struct iic_msg msgs[] = { + { slave, IIC_M_WR | IIC_M_NOSTOP, 1, &cmd }, + { slave, IIC_M_RD | IIC_M_NOSTOP, 1, &len }, + }; + struct iic_msg block_msg[] = { + { slave, IIC_M_RD | IIC_M_NOSTART, 0, NULL }, + }; + device_t parent = device_get_parent(dev); + int error; + + /* Have to do this because the command is split in two transfers. */ + error = iicbus_request_bus(parent, dev, IIC_WAIT); + if (error == 0) + error = iicbus_transfer(dev, msgs, nitems(msgs)); + if (error == 0) { + /* + * If the slave offers an empty reply, + * read one byte to generate the stop or abort. + */ + if (len == 0) + block_msg[0].len = 1; + else + block_msg[0].len = len; + if (len <= sizeof(local_buffer)) + block_msg[0].buf = local_buffer; + else + block_msg[0].buf = malloc(len, M_DEVBUF, M_WAITOK); + error = iicbus_transfer(dev, block_msg, nitems(block_msg)); + if (len == 0) + error = errno2iic(EBADMSG); + if (error == 0) { + *count = len; + memcpy(buf, block_msg[0].buf, len); + } + if (block_msg[0].buf != local_buffer) + free(block_msg[0].buf, M_DEVBUF); + } + (void)iicbus_release_bus(parent, dev); + return (error); +} + +static ACPI_STATUS +acpi_iicbus_space_handler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, UINT64 *Value, void *HandlerContext, void *RegionContext) +{ + struct gsb_buffer *gsb; + struct acpi_iicbus_softc *sc; + device_t dev; + ACPI_CONNECTION_INFO *info; + ACPI_RESOURCE_I2C_SERIALBUS *sb; + ACPI_RESOURCE *res; + ACPI_STATUS s; + int val; + + gsb = (struct gsb_buffer *)Value; + if (gsb == NULL) + return (AE_BAD_PARAMETER); + + info = HandlerContext; + s = AcpiBufferToResource(info->Connection, info->Length, &res); + if (ACPI_FAILURE(s)) + return (s); + + if (!acpi_resource_is_i2c_serialbus(res)) { + s = AE_BAD_PARAMETER; + goto err; + } + + sb = &res->Data.I2cSerialBus; + + /* XXX Ignore 10bit addressing for now */ + if (sb->AccessMode == ACPI_I2C_10BIT_MODE) { + s = AE_BAD_PARAMETER; + goto err; + } + +#define AML_FIELD_ATTRIB_MASK 0x0F +#define AML_FIELD_ATTRIO(attr, io) (((attr) << 16) | (io)) + + Function &= AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_MASK, ACPI_IO_MASK); + sc = __containerof(info, struct acpi_iicbus_softc, space_handler_info); + dev = sc->super_sc.dev; + + switch (Function) { + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_SEND_RECEIVE, ACPI_READ): + val = acpi_iicbus_recvb(dev, sb->SlaveAddress, gsb->data); + break; + + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_SEND_RECEIVE, ACPI_WRITE): + val = acpi_iicbus_sendb(dev, sb->SlaveAddress, gsb->data[0]); + break; + + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_BYTE, ACPI_READ): + val = acpi_iicbus_read(dev, sb->SlaveAddress, Address, + gsb->data, 1); + break; + + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_BYTE, ACPI_WRITE): + val = acpi_iicbus_write(dev, sb->SlaveAddress, Address, + gsb->data, 1); + break; + + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_WORD, ACPI_READ): + val = acpi_iicbus_read(dev, sb->SlaveAddress, Address, + gsb->data, 2); + break; + + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_WORD, ACPI_WRITE): + val = acpi_iicbus_write(dev, sb->SlaveAddress, Address, + gsb->data, 2); + break; + + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_BLOCK, ACPI_READ): + val = acpi_iicbus_bread(dev, sb->SlaveAddress, Address, + &gsb->len, gsb->data); + break; + + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_BLOCK, ACPI_WRITE): + val = acpi_iicbus_bwrite(dev, sb->SlaveAddress, Address, + gsb->len, gsb->data); + break; + + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_BYTES, ACPI_READ): + val = acpi_iicbus_read(dev, sb->SlaveAddress, Address, + gsb->data, info->AccessLength); + break; + + case AML_FIELD_ATTRIO(AML_FIELD_ATTRIB_BYTES, ACPI_WRITE): + val = acpi_iicbus_write(dev, sb->SlaveAddress, Address, + gsb->data, info->AccessLength); + break; + + default: + device_printf(dev, "protocol(0x%04x) is not supported.\n", + Function); + s = AE_BAD_PARAMETER; + goto err; + } + + gsb->status = val; + +err: + ACPI_FREE(res); + + return (s); +} + +static int +acpi_iicbus_install_address_space_handler(struct acpi_iicbus_softc *sc) +{ + ACPI_HANDLE handle; + ACPI_STATUS s; + + handle = acpi_get_handle(device_get_parent(sc->super_sc.dev)); + s = AcpiInstallAddressSpaceHandler(handle, ACPI_ADR_SPACE_GSBUS, + &acpi_iicbus_space_handler, NULL, &sc->space_handler_info); + if (ACPI_FAILURE(s)) { + device_printf(sc->super_sc.dev, + "Failed to install GSBUS Address Space Handler in ACPI\n"); + return (ENXIO); + } + + return (0); +} + +static int +acpi_iicbus_remove_address_space_handler(struct acpi_iicbus_softc *sc) +{ + ACPI_HANDLE handle; + ACPI_STATUS s; + + handle = acpi_get_handle(device_get_parent(sc->super_sc.dev)); + s = AcpiRemoveAddressSpaceHandler(handle, ACPI_ADR_SPACE_GSBUS, + &acpi_iicbus_space_handler); + if (ACPI_FAILURE(s)) { + device_printf(sc->super_sc.dev, + "Failed to remove GSBUS Address Space Handler from ACPI\n"); + return (ENXIO); + } + + return (0); +} + +static ACPI_STATUS +acpi_iicbus_get_i2cres_cb(ACPI_RESOURCE *res, void *context) +{ + ACPI_IICBUS_RESOURCE_I2C_SERIALBUS *sb = context; + ACPI_STATUS status; + ACPI_HANDLE handle; + + if (acpi_resource_is_i2c_serialbus(res)) { + status = AcpiGetHandle(ACPI_ROOT_OBJECT, + res->Data.I2cSerialBus.ResourceSource.StringPtr, &handle); + if (ACPI_FAILURE(status)) + return (status); + memcpy(sb, &res->Data.I2cSerialBus, + sizeof(ACPI_IICBUS_RESOURCE_I2C_SERIALBUS)); + /* + * replace "pointer to ACPI object name string" field + * with pointer to ACPI object itself. + */ + sb->ResourceSource_Handle = handle; + return (AE_CTRL_TERMINATE); + } else if (res->Type == ACPI_RESOURCE_TYPE_END_TAG) + return (AE_NOT_FOUND); + + return (AE_OK); +} + +static ACPI_STATUS +acpi_iicbus_get_i2cres(ACPI_HANDLE handle, ACPI_RESOURCE_I2C_SERIALBUS *sb) +{ + + return (AcpiWalkResources(handle, "_CRS", + acpi_iicbus_get_i2cres_cb, sb)); +} + +static ACPI_STATUS +acpi_iicbus_parse_resources_cb(ACPI_RESOURCE *res, void *context) +{ + device_t dev = context; + struct iicbus_ivar *super_devi = device_get_ivars(dev); + struct resource_list *rl = &super_devi->rl; + int irq, gpio_pin; + + switch(res->Type) { + case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: + if (res->Data.ExtendedIrq.InterruptCount > 0) { + irq = res->Data.ExtendedIrq.Interrupts[0]; + if (bootverbose) + printf(" IRQ: %d\n", irq); + resource_list_add_next(rl, SYS_RES_IRQ, irq, irq, 1); + return (AE_CTRL_TERMINATE); + } + break; + case ACPI_RESOURCE_TYPE_GPIO: + if (res->Data.Gpio.ConnectionType == + ACPI_RESOURCE_GPIO_TYPE_INT) { + /* Not supported by FreeBSD yet */ + gpio_pin = res->Data.Gpio.PinTable[0]; + if (bootverbose) + printf(" GPIO IRQ pin: %d\n", gpio_pin); + return (AE_CTRL_TERMINATE); + } + break; + default: + break; + } + + return (AE_OK); +} + +static ACPI_STATUS +acpi_iicbus_parse_resources(ACPI_HANDLE handle, device_t dev) +{ + + return (AcpiWalkResources(handle, "_CRS", + acpi_iicbus_parse_resources_cb, dev)); +} + +static void +acpi_iicbus_dump_res(device_t dev, ACPI_IICBUS_RESOURCE_I2C_SERIALBUS *sb) +{ + device_printf(dev, "found ACPI child\n"); + printf(" SlaveAddress: 0x%04hx\n", sb->SlaveAddress); + printf(" ConnectionSpeed: %uHz\n", sb->ConnectionSpeed); + printf(" SlaveMode: %s\n", + sb->SlaveMode == ACPI_CONTROLLER_INITIATED ? + "ControllerInitiated" : "DeviceInitiated"); + printf(" AddressingMode: %uBit\n", sb->AccessMode == 0 ? 7 : 10); + printf(" ConnectionSharing: %s\n", sb->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_child(ACPI_HANDLE handle, UINT32 level, + void *context, void **result) +{ + device_t iicbus, child, acpi_child, acpi0; + struct iicbus_softc *super_sc; + ACPI_IICBUS_RESOURCE_I2C_SERIALBUS sb; + ACPI_STATUS status; + UINT32 sta; + + iicbus = context; + super_sc = device_get_softc(iicbus); + + /* + * 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(&sb, sizeof(ACPI_IICBUS_RESOURCE_I2C_SERIALBUS)); + if (ACPI_FAILURE(acpi_iicbus_get_i2cres(handle, &sb)) || + sb.SlaveAddress == 0) + return (AE_OK); + if (sb.ResourceSource_Handle != + acpi_get_handle(device_get_parent(iicbus))) + return (AE_OK); + if (bootverbose) + acpi_iicbus_dump_res(iicbus, &sb); + + /* Find out speed of the slowest slave */ + if (super_sc->bus_freq == 0 || super_sc->bus_freq > sb.ConnectionSpeed) + super_sc->bus_freq = sb.ConnectionSpeed; + + /* Delete existing child of acpi bus */ + acpi_child = acpi_get_device(handle); + if (acpi_child != NULL) { + acpi0 = devclass_get_device(devclass_find("acpi"), 0); + if (device_get_parent(acpi_child) != acpi0) + return (AE_OK); + + if (device_is_attached(acpi_child)) + return (AE_OK); + + if (device_delete_child(acpi0, acpi_child) != 0) + return (AE_OK); + } + + 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, sb.SlaveAddress); + acpi_set_handle(child, handle); + (void)acpi_iicbus_parse_resources(handle, child); + + /* + * Update ACPI-CA to use the IIC enumerated device_t for this handle. + */ + status = AcpiAttachData(handle, acpi_fake_objhandler, child); + if (ACPI_FAILURE(status)) + printf("WARNING: Unable to attach object data to %s - %s\n", + acpi_name(handle), AcpiFormatException(status)); + + return (AE_OK); +} + +static ACPI_STATUS +acpi_iicbus_enumerate_children(device_t dev) +{ + + return (AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, acpi_iicbus_enumerate_child, NULL, dev, NULL)); +} + +static void +acpi_iicbus_set_power_children(device_t dev, int state, bool all_children) +{ + device_t *devlist; + int i, numdevs; + + if (device_get_children(dev, &devlist, &numdevs) != 0) + return; + + for (i = 0; i < numdevs; i++) + if (all_children || device_is_attached(devlist[i]) != 0) + acpi_set_powerstate(devlist[i], state); + + free(devlist, M_TEMP); +} + +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 acpi_iicbus_softc *sc = device_get_softc(dev); + int error; + + device_set_desc(dev, "Philips I2C bus (ACPI-hinted)"); + + if (ACPI_FAILURE(acpi_iicbus_enumerate_children(dev))) + device_printf(dev, "children enumeration failed\n"); + + acpi_iicbus_set_power_children(dev, ACPI_STATE_D0, true); + error = iicbus_attach_common(dev, sc->super_sc.bus_freq); + if (error == 0 && install_space_handler != 0 && + acpi_iicbus_install_address_space_handler(sc) == 0) + sc->space_handler_installed = true; + + return (error); +} + +static int +acpi_iicbus_detach(device_t dev) +{ + struct acpi_iicbus_softc *sc = device_get_softc(dev); + + if (sc->space_handler_installed) + acpi_iicbus_remove_address_space_handler(sc); + acpi_iicbus_set_power_children(dev, ACPI_STATE_D3, false); + + return (iicbus_detach(dev)); +} + +static int +acpi_iicbus_suspend(device_t dev) +{ + int error; + + error = bus_generic_suspend(dev); + if (error == 0) + acpi_iicbus_set_power_children(dev, ACPI_STATE_D3, false); + + return (error); +} + +static int +acpi_iicbus_resume(device_t dev) +{ + + acpi_iicbus_set_power_children(dev, ACPI_STATE_D0, false); + + return (bus_generic_resume(dev)); +} + +/* + * If this device is an ACPI child but no one claimed it, attempt + * to power it off. We'll power it back up when a driver is added. + */ +static void +acpi_iicbus_probe_nomatch(device_t bus, device_t child) +{ + + iicbus_probe_nomatch(bus, child); + acpi_set_powerstate(child, ACPI_STATE_D3); +} + +/* + * If a new driver has a chance to probe a child, first power it up. + */ +static void +acpi_iicbus_driver_added(device_t dev, driver_t *driver) +{ + device_t child, *devlist; + int i, numdevs; + + DEVICE_IDENTIFY(driver, dev); + if (device_get_children(dev, &devlist, &numdevs) != 0) + return; + + for (i = 0; i < numdevs; i++) { + child = devlist[i]; + if (device_get_state(child) == DS_NOTPRESENT) { + acpi_set_powerstate(child, ACPI_STATE_D0); + if (device_probe_and_attach(child) != 0) + acpi_set_powerstate(child, ACPI_STATE_D3); + } + } + free(devlist, M_TEMP); +} + +static void +acpi_iicbus_child_deleted(device_t bus, device_t child) +{ + struct acpi_iicbus_ivars *devi = device_get_ivars(child); + + if (acpi_get_device(devi->handle) == child) + AcpiDetachData(devi->handle, acpi_fake_objhandler); +} + +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); +} + +/* 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) +{ + struct acpi_iicbus_ivars *devi = device_get_ivars(child); + int error; + + /* read IIC location hint string into the buffer. */ + error = iicbus_child_location_str(bus, child, buf, buflen); + if (error != 0) + return (error); + + /* Place ACPI string right after IIC one's terminating NUL. */ + if (devi->handle != NULL && + ((buf[0] != '\0' && strlcat(buf, " ", buflen) >= buflen) || + strlcat(buf, "handle=", buflen) >= buflen || + strlcat(buf, acpi_name(devi->handle), buflen) >= buflen)) + return (EOVERFLOW); + + 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) +{ + struct acpi_iicbus_ivars *devi = device_get_ivars(child); + size_t acpi_offset; + int error; + + /* read IIC PnP string into the buffer. */ + error = iicbus_child_pnpinfo_str(bus, child, buf, buflen); + if (error != 0) + return (error); + + if (devi->handle == NULL) + return (0); + + /* Place ACPI string right after IIC one's terminating NUL. */ + acpi_offset = strlen(buf); + if (acpi_offset != 0) + acpi_offset++; + error = acpi_pnpinfo_str(devi->handle, buf + acpi_offset, + buflen - acpi_offset); + + /* Coalesce both strings if they are not empty. */ + if (acpi_offset > 0 && acpi_offset < buflen && buf[acpi_offset] != 0) + buf[acpi_offset - 1] = ' '; + + return (error); +} + +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), + DEVMETHOD(device_suspend, acpi_iicbus_suspend), + DEVMETHOD(device_resume, acpi_iicbus_resume), + + /* Bus interface */ + DEVMETHOD(bus_add_child, acpi_iicbus_add_child), + DEVMETHOD(bus_probe_nomatch, acpi_iicbus_probe_nomatch), + DEVMETHOD(bus_driver_added, acpi_iicbus_driver_added), + DEVMETHOD(bus_child_deleted, acpi_iicbus_child_deleted), + 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 acpi_iicbus_softc), iicbus_driver); +MODULE_VERSION(acpi_iicbus, 1); +MODULE_DEPEND(acpi_iicbus, acpi, 1, 1, 1); Index: head/sys/dev/iicbus/iicbus.h =================================================================== --- head/sys/dev/iicbus/iicbus.h +++ head/sys/dev/iicbus/iicbus.h @@ -57,6 +57,7 @@ struct resource_list rl; }; +/* Value of 0x100 is reserved for ACPI_IVAR_HANDLE used by acpi_iicbus */ enum { IICBUS_IVAR_ADDR /* Address or base address */ }; @@ -79,9 +80,24 @@ 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_detach(device_t dev); +void iicbus_probe_nomatch(device_t bus, device_t child); +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; extern devclass_t ofw_iicbus_devclass; +extern driver_t acpi_iicbus_driver; #endif Index: head/sys/dev/iicbus/iicbus.c =================================================================== --- head/sys/dev/iicbus/iicbus.c +++ head/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) @@ -131,6 +131,13 @@ } static int +iicbus_attach(device_t dev) +{ + + return (iicbus_attach_common(dev, 0)); +} + +int iicbus_detach(device_t dev) { struct iicbus_softc *sc = IICBUS_SOFTC(dev); @@ -158,7 +165,7 @@ return (retval); } -static void +void iicbus_probe_nomatch(device_t bus, device_t child) { struct iicbus_ivar *devi = IICBUS_IVAR(child); @@ -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); @@ -232,6 +240,14 @@ resource_list_init(&devi->rl); device_set_ivars(child, devi); 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 Index: head/sys/modules/i2c/iicbus/Makefile =================================================================== --- head/sys/modules/i2c/iicbus/Makefile +++ head/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