2021-11-10 18:39:03 +03:00
|
|
|
/* $NetBSD: i2cmux.c,v 1.7 2021/11/10 15:39:03 msaitoh Exp $ */
|
2020-12-28 23:29:57 +03:00
|
|
|
|
|
|
|
/*-
|
|
|
|
* Copyright (c) 2020 The NetBSD Foundation, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
|
|
* by Jason R. Thorpe.
|
|
|
|
*
|
|
|
|
* 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.
|
2021-01-24 22:35:21 +03:00
|
|
|
*
|
2020-12-28 23:29:57 +03:00
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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
|
2021-01-24 22:35:21 +03:00
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
2020-12-28 23:29:57 +03:00
|
|
|
|
2021-01-25 15:18:18 +03:00
|
|
|
#if defined(__i386__) || defined(__amd64__) || defined(__aarch64__)
|
|
|
|
#include "acpica.h"
|
|
|
|
#endif
|
|
|
|
|
2020-12-28 23:29:57 +03:00
|
|
|
#include <sys/cdefs.h>
|
2021-11-10 18:39:03 +03:00
|
|
|
__KERNEL_RCSID(0, "$NetBSD: i2cmux.c,v 1.7 2021/11/10 15:39:03 msaitoh Exp $");
|
2020-12-28 23:29:57 +03:00
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/device.h>
|
|
|
|
#include <sys/kernel.h>
|
2021-01-24 22:35:21 +03:00
|
|
|
#include <sys/kmem.h>
|
2020-12-28 23:29:57 +03:00
|
|
|
|
|
|
|
#include <dev/fdt/fdtvar.h>
|
|
|
|
#include <dev/i2c/i2cvar.h>
|
|
|
|
#include <dev/i2c/i2cmuxvar.h>
|
|
|
|
|
2021-01-25 15:18:18 +03:00
|
|
|
#if NACPICA > 0
|
|
|
|
#include <dev/acpi/acpivar.h>
|
|
|
|
#include <dev/acpi/acpi_i2c.h>
|
|
|
|
#endif
|
|
|
|
|
2021-01-24 22:35:21 +03:00
|
|
|
/*
|
|
|
|
* i2c mux
|
|
|
|
*
|
2020-12-28 23:29:57 +03:00
|
|
|
* This works by interposing a set of virtual controllers behind the real
|
|
|
|
* i2c controller. We provide our own acquire and release functions that
|
|
|
|
* perform the following tasks:
|
2021-01-24 22:35:21 +03:00
|
|
|
*
|
2020-12-28 23:29:57 +03:00
|
|
|
* acquire -> acquire parent controller, program mux
|
2021-01-24 22:35:21 +03:00
|
|
|
*
|
2020-12-28 23:29:57 +03:00
|
|
|
* release -> idle mux, release parent controller
|
2021-01-24 22:35:21 +03:00
|
|
|
*
|
2020-12-28 23:29:57 +03:00
|
|
|
* All of the actual I/O operations are transparently passed through.
|
2021-01-24 22:35:21 +03:00
|
|
|
*
|
|
|
|
* N.B. the locking order; the generic I2C layer has already acquired
|
2020-12-28 23:29:57 +03:00
|
|
|
* our virtual controller's mutex before calling our acquire function,
|
2021-01-24 22:35:21 +03:00
|
|
|
* and we will then acquire the real controller's mutex when we acquire
|
2020-12-28 23:29:57 +03:00
|
|
|
* the bus, so the order is:
|
2021-01-24 22:35:21 +03:00
|
|
|
*
|
2020-12-28 23:29:57 +03:00
|
|
|
* mux virtual controller -> parent controller
|
|
|
|
*
|
|
|
|
* These are common routines used by various i2c mux controller
|
|
|
|
* implementations (gpio, pin mux, i2c device, etc.).
|
2021-01-24 22:35:21 +03:00
|
|
|
*/
|
2020-12-28 23:29:57 +03:00
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
static int
|
|
|
|
iicmux_acquire_bus(void * const v, int const flags)
|
|
|
|
{
|
|
|
|
struct iicmux_bus * const bus = v;
|
|
|
|
struct iicmux_softc * const sc = bus->mux;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
error = iic_acquire_bus(sc->sc_i2c_parent, flags);
|
|
|
|
if (error) {
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
error = sc->sc_config->acquire_bus(bus, flags);
|
|
|
|
if (error) {
|
|
|
|
iic_release_bus(sc->sc_i2c_parent, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
iicmux_release_bus(void * const v, int const flags)
|
|
|
|
{
|
|
|
|
struct iicmux_bus * const bus = v;
|
|
|
|
struct iicmux_softc * const sc = bus->mux;
|
|
|
|
|
|
|
|
sc->sc_config->release_bus(bus, flags);
|
|
|
|
iic_release_bus(sc->sc_i2c_parent, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
iicmux_exec(void * const v, i2c_op_t const op, i2c_addr_t const addr,
|
|
|
|
const void * const cmdbuf, size_t const cmdlen, void * const databuf,
|
|
|
|
size_t const datalen, int const flags)
|
|
|
|
{
|
|
|
|
struct iicmux_bus * const bus = v;
|
|
|
|
struct iicmux_softc * const sc = bus->mux;
|
|
|
|
|
|
|
|
return iic_exec(sc->sc_i2c_parent, op, addr, cmdbuf, cmdlen,
|
|
|
|
databuf, datalen, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
static int
|
|
|
|
iicmux_count_children(struct iicmux_softc * const sc)
|
|
|
|
{
|
|
|
|
char name[32];
|
|
|
|
int child, count;
|
|
|
|
|
|
|
|
restart:
|
|
|
|
for (child = OF_child(sc->sc_i2c_mux_phandle), count = 0; child;
|
|
|
|
child = OF_peer(child)) {
|
|
|
|
if (OF_getprop(child, "name", name, sizeof(name)) <= 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (strcmp(name, "i2c-mux") == 0) {
|
|
|
|
/*
|
2021-11-10 18:39:03 +03:00
|
|
|
* The node we encountered is the actual parent
|
2020-12-28 23:29:57 +03:00
|
|
|
* of the i2c bus children. Stash its phandle
|
|
|
|
* and restart the enumeration.
|
|
|
|
*/
|
|
|
|
sc->sc_i2c_mux_phandle = child;
|
|
|
|
goto restart;
|
|
|
|
}
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* XXX iicbus_print() should be able to do this. */
|
|
|
|
static int
|
|
|
|
iicmux_print(void * const aux, const char * const pnp)
|
2021-01-24 22:35:21 +03:00
|
|
|
{
|
2020-12-28 23:29:57 +03:00
|
|
|
i2c_tag_t const tag = aux;
|
|
|
|
struct iicmux_bus * const bus = tag->ic_cookie;
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
rv = iicbus_print(aux, pnp);
|
|
|
|
aprint_normal(" bus %d", bus->busidx);
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2021-01-24 22:35:21 +03:00
|
|
|
static void
|
2020-12-28 23:29:57 +03:00
|
|
|
iicmux_attach_bus(struct iicmux_softc * const sc,
|
2021-01-25 15:18:18 +03:00
|
|
|
uintptr_t const handle, enum i2c_cookie_type handletype, int const busidx)
|
2020-12-28 23:29:57 +03:00
|
|
|
{
|
|
|
|
struct iicmux_bus * const bus = &sc->sc_busses[busidx];
|
|
|
|
|
|
|
|
bus->mux = sc;
|
|
|
|
bus->busidx = busidx;
|
2021-01-25 15:18:18 +03:00
|
|
|
bus->handle = handle;
|
|
|
|
bus->handletype = handletype;
|
2020-12-28 23:29:57 +03:00
|
|
|
|
|
|
|
bus->bus_data = sc->sc_config->get_bus_info(bus);
|
|
|
|
if (bus->bus_data == NULL) {
|
|
|
|
aprint_error_dev(sc->sc_dev,
|
|
|
|
"unable to get info for bus %d\n", busidx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
iic_tag_init(&bus->controller);
|
|
|
|
bus->controller.ic_cookie = bus;
|
|
|
|
bus->controller.ic_acquire_bus = iicmux_acquire_bus;
|
|
|
|
bus->controller.ic_release_bus = iicmux_release_bus;
|
|
|
|
bus->controller.ic_exec = iicmux_exec;
|
|
|
|
|
2021-01-25 15:18:18 +03:00
|
|
|
switch (handletype) {
|
|
|
|
case I2C_COOKIE_OF:
|
|
|
|
fdtbus_register_i2c_controller(&bus->controller,
|
|
|
|
(int)bus->handle);
|
|
|
|
|
|
|
|
fdtbus_attach_i2cbus(sc->sc_dev, (int)bus->handle,
|
|
|
|
&bus->controller, iicmux_print);
|
|
|
|
break;
|
|
|
|
#if NACPICA > 0
|
|
|
|
case I2C_COOKIE_ACPI: {
|
|
|
|
struct acpi_devnode *ad = acpi_match_node((ACPI_HANDLE)handle);
|
|
|
|
KASSERT(ad != NULL);
|
|
|
|
struct i2cbus_attach_args iba = {
|
|
|
|
.iba_tag = &bus->controller,
|
2021-01-26 03:19:52 +03:00
|
|
|
.iba_child_devices = acpi_enter_i2c_devs(NULL, ad)
|
2021-01-25 15:18:18 +03:00
|
|
|
};
|
2021-08-07 19:18:40 +03:00
|
|
|
config_found(sc->sc_dev, &iba, iicbus_print, CFARGS_NONE);
|
2021-01-25 15:18:18 +03:00
|
|
|
} break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
aprint_error_dev(sc->sc_dev, "unknown handle type\n");
|
|
|
|
break;
|
|
|
|
}
|
2020-12-28 23:29:57 +03:00
|
|
|
}
|
|
|
|
|
2021-01-25 15:18:18 +03:00
|
|
|
static void
|
|
|
|
iicmux_attach_fdt(struct iicmux_softc * const sc)
|
2020-12-28 23:29:57 +03:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We start out assuming that the i2c bus nodes are children of
|
|
|
|
* our own node. We'll adjust later if we encounter an "i2c-mux"
|
|
|
|
* node when counting our children. If we encounter such a node,
|
|
|
|
* then it's that node that is the parent of the i2c bus children.
|
|
|
|
*/
|
2021-01-25 15:18:18 +03:00
|
|
|
sc->sc_i2c_mux_phandle = (int)sc->sc_handle;
|
2020-12-28 23:29:57 +03:00
|
|
|
|
|
|
|
sc->sc_nbusses = iicmux_count_children(sc);
|
|
|
|
if (sc->sc_nbusses == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sc->sc_busses = kmem_zalloc(sizeof(*sc->sc_busses) * sc->sc_nbusses,
|
|
|
|
KM_SLEEP);
|
|
|
|
|
|
|
|
int child, idx;
|
|
|
|
for (child = OF_child(sc->sc_i2c_mux_phandle), idx = 0; child;
|
|
|
|
child = OF_peer(child), idx++) {
|
|
|
|
KASSERT(idx < sc->sc_nbusses);
|
2021-01-25 15:18:18 +03:00
|
|
|
iicmux_attach_bus(sc, child, I2C_COOKIE_OF, idx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if NACPICA > 0
|
|
|
|
static void
|
|
|
|
iicmux_attach_acpi(struct iicmux_softc * const sc)
|
|
|
|
{
|
|
|
|
ACPI_HANDLE hdl = (ACPI_HANDLE)sc->sc_handle;
|
|
|
|
struct acpi_devnode *devnode, *ad;
|
|
|
|
int idx;
|
|
|
|
|
|
|
|
devnode = acpi_match_node(hdl);
|
|
|
|
KASSERT(devnode != NULL);
|
|
|
|
|
|
|
|
/* Count child busses */
|
|
|
|
sc->sc_nbusses = 0;
|
|
|
|
SIMPLEQ_FOREACH(ad, &devnode->ad_child_head, ad_child_list) {
|
|
|
|
if (ad->ad_devinfo->Type != ACPI_TYPE_DEVICE ||
|
|
|
|
!acpi_device_present(ad->ad_handle)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
sc->sc_nbusses++;
|
|
|
|
}
|
|
|
|
|
|
|
|
sc->sc_busses = kmem_zalloc(sizeof(*sc->sc_busses) * sc->sc_nbusses,
|
|
|
|
KM_SLEEP);
|
|
|
|
|
|
|
|
/* Attach child busses */
|
|
|
|
idx = 0;
|
|
|
|
SIMPLEQ_FOREACH(ad, &devnode->ad_child_head, ad_child_list) {
|
|
|
|
if (ad->ad_devinfo->Type != ACPI_TYPE_DEVICE ||
|
|
|
|
!acpi_device_present(ad->ad_handle)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
iicmux_attach_bus(sc, (uintptr_t)ad->ad_handle,
|
|
|
|
I2C_COOKIE_ACPI, idx);
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void
|
|
|
|
iicmux_attach(struct iicmux_softc * const sc)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We expect sc->sc_handle, sc->sc_config, and sc->sc_i2c_parent
|
|
|
|
* to be initialized by the front-end.
|
|
|
|
*/
|
|
|
|
KASSERT(sc->sc_handle > 0);
|
|
|
|
KASSERT(sc->sc_config != NULL);
|
|
|
|
KASSERT(sc->sc_i2c_parent != NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Gather up all of the various bits of information needed
|
|
|
|
* for this particular type of i2c mux.
|
|
|
|
*/
|
|
|
|
sc->sc_mux_data = sc->sc_config->get_mux_info(sc);
|
|
|
|
if (sc->sc_mux_data == NULL) {
|
|
|
|
aprint_error_dev(sc->sc_dev, "unable to get info for mux\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do configuration method (OF, ACPI) specific setup.
|
|
|
|
*/
|
|
|
|
switch (sc->sc_handletype) {
|
|
|
|
case I2C_COOKIE_OF:
|
|
|
|
iicmux_attach_fdt(sc);
|
|
|
|
break;
|
|
|
|
#if NACPICA > 0
|
|
|
|
case I2C_COOKIE_ACPI:
|
|
|
|
iicmux_attach_acpi(sc);
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
aprint_error_dev(sc->sc_dev, "could not configure mux: "
|
|
|
|
"handle type %u not supported\n", sc->sc_handletype);
|
|
|
|
break;
|
2020-12-28 23:29:57 +03:00
|
|
|
}
|
|
|
|
}
|