#include <sys/cdefs.h>
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#ifdef FDT
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/ofw/openfirm.h>
#endif
#include <dev/iicbus/iicbus.h>
#include <dev/iicbus/iiconf.h>
#include "iicbus_if.h"
#include "iicmux_if.h"
#include <dev/iicbus/mux/iicmux.h>
enum pca954x_type {
PCA954X_MUX,
PCA954X_SW,
};
struct pca954x_descr {
const char *partname;
const char *description;
enum pca954x_type type;
uint8_t numchannels;
uint8_t enable;
};
static struct pca954x_descr pca9540_descr = {
.partname = "pca9540",
.description = "PCA9540B I2C Mux",
.type = PCA954X_MUX,
.numchannels = 2,
.enable = 0x04,
};
static struct pca954x_descr pca9546_descr = {
.partname = "pca9546",
.description = "PCA9546 I2C Switch",
.type = PCA954X_SW,
.numchannels = 4,
};
static struct pca954x_descr pca9547_descr = {
.partname = "pca9547",
.description = "PCA9547 I2C Mux",
.type = PCA954X_MUX,
.numchannels = 8,
.enable = 0x08,
};
static struct pca954x_descr pca9548_descr = {
.partname = "pca9548",
.description = "PCA9548A I2C Switch",
.type = PCA954X_SW,
.numchannels = 8,
};
#ifdef FDT
static struct ofw_compat_data compat_data[] = {
{ "nxp,pca9540", (uintptr_t)&pca9540_descr },
{ "nxp,pca9546", (uintptr_t)&pca9546_descr },
{ "nxp,pca9547", (uintptr_t)&pca9547_descr },
{ "nxp,pca9548", (uintptr_t)&pca9548_descr },
{ NULL, 0 },
};
#else
static struct pca954x_descr *part_descrs[] = {
&pca9540_descr,
&pca9546_descr,
&pca9547_descr,
&pca9548_descr,
};
#endif
struct pca954x_softc {
struct iicmux_softc mux;
const struct pca954x_descr *descr;
uint8_t addr;
bool idle_disconnect;
};
static int
pca954x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd)
{
struct pca954x_softc *sc;
struct iic_msg msg;
int error;
uint8_t busbits;
sc = device_get_softc(dev);
if (busidx == IICMUX_SELECT_IDLE) {
if (sc->idle_disconnect)
busbits = 0;
else
return (0);
} else if (sc->descr->type == PCA954X_MUX) {
uint8_t en;
en = sc->descr->enable;
KASSERT(en > 0 && powerof2(en), ("%s: %s enable %#x "
"invalid\n", __func__, sc->descr->partname, en));
busbits = en | (busidx & (en - 1));
} else if (sc->descr->type == PCA954X_SW) {
busbits = 1u << busidx;
} else {
panic("%s: %s: unsupported type %d\n",
__func__, sc->descr->partname, sc->descr->type);
}
msg.slave = sc->addr;
msg.flags = IIC_M_WR;
msg.len = 1;
msg.buf = &busbits;
error = iicbus_transfer(dev, &msg, 1);
return (error);
}
static const struct pca954x_descr *
pca954x_find_chip(device_t dev)
{
#ifdef FDT
const struct ofw_compat_data *compat;
if (!ofw_bus_status_okay(dev))
return (NULL);
compat = ofw_bus_search_compatible(dev, compat_data);
if (compat == NULL)
return (NULL);
return ((const struct pca954x_descr *)compat->ocd_data);
#else
const char *type;
int i;
if (resource_string_value(device_get_name(dev), device_get_unit(dev),
"chip_type", &type) == 0) {
for (i = 0; i < nitems(part_descrs) - 1; ++i) {
if (strcasecmp(type, part_descrs[i]->partname) == 0)
return (part_descrs[i]);
}
}
return (NULL);
#endif
}
static int
pca954x_probe(device_t dev)
{
const struct pca954x_descr *descr;
descr = pca954x_find_chip(dev);
if (descr == NULL)
return (ENXIO);
device_set_desc(dev, descr->description);
return (BUS_PROBE_DEFAULT);
}
static int
pca954x_attach(device_t dev)
{
struct pca954x_softc *sc;
const struct pca954x_descr *descr;
int error;
sc = device_get_softc(dev);
sc->addr = iicbus_get_addr(dev);
sc->idle_disconnect = device_has_property(dev, "i2c-mux-idle-disconnect");
sc->descr = descr = pca954x_find_chip(dev);
error = iicmux_attach(dev, device_get_parent(dev), descr->numchannels);
if (error == 0)
bus_attach_children(dev);
return (error);
}
static int
pca954x_detach(device_t dev)
{
int error;
error = iicmux_detach(dev);
return (error);
}
static device_method_t pca954x_methods[] = {
DEVMETHOD(device_probe, pca954x_probe),
DEVMETHOD(device_attach, pca954x_attach),
DEVMETHOD(device_detach, pca954x_detach),
DEVMETHOD(iicmux_bus_select, pca954x_bus_select),
DEVMETHOD_END
};
DEFINE_CLASS_1(pca954x, pca954x_driver, pca954x_methods,
sizeof(struct pca954x_softc), iicmux_driver);
DRIVER_MODULE(pca954x, iicbus, pca954x_driver, 0, 0);
#ifdef FDT
DRIVER_MODULE(ofw_iicbus, pca954x, ofw_iicbus_driver, 0, 0);
#else
DRIVER_MODULE(iicbus, pca954x, iicbus_driver, 0, 0);
#endif
MODULE_DEPEND(pca954x, iicmux, 1, 1, 1);
MODULE_DEPEND(pca954x, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
MODULE_VERSION(pca954x, 1);
#ifdef FDT
IICBUS_FDT_PNP_INFO(compat_data);
#endif