#include <sys/cdefs.h>
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/gpio.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <dev/gpio/gpiobusvar.h>
#include <dev/iicbus/iiconf.h>
#include "gpiobus_if.h"
#include "iicbb_if.h"
#define GPIOIIC_SCL_DFLT 0
#define GPIOIIC_SDA_DFLT 1
#define GPIOIIC_MIN_PINS 2
struct gpioiic_softc
{
device_t dev;
gpio_pin_t sclpin;
gpio_pin_t sdapin;
};
#ifdef FDT
#include <dev/ofw/ofw_bus.h>
static struct ofw_compat_data compat_data[] = {
{"i2c-gpio", true},
{"gpioiic", true},
{NULL, false}
};
OFWBUS_PNP_INFO(compat_data);
SIMPLEBUS_PNP_INFO(compat_data);
static phandle_t
gpioiic_get_node(device_t bus, device_t dev)
{
return (ofw_bus_get_node(bus));
}
static int
gpioiic_setup_fdt_pins(struct gpioiic_softc *sc)
{
phandle_t node;
int err;
node = ofw_bus_get_node(sc->dev);
if (OF_hasprop(node, "gpios")) {
if ((err = gpio_pin_get_by_ofw_idx(sc->dev, node,
GPIOIIC_SCL_DFLT, &sc->sclpin)) != 0) {
device_printf(sc->dev, "invalid gpios property\n");
return (err);
}
if ((err = gpio_pin_get_by_ofw_idx(sc->dev, node,
GPIOIIC_SDA_DFLT, &sc->sdapin)) != 0) {
device_printf(sc->dev, "ivalid gpios property\n");
return (err);
}
} else {
if ((err = gpio_pin_get_by_ofw_property(sc->dev, node,
"scl-gpios", &sc->sclpin)) != 0) {
device_printf(sc->dev, "missing scl-gpios property\n");
return (err);
}
if ((err = gpio_pin_get_by_ofw_property(sc->dev, node,
"sda-gpios", &sc->sdapin)) != 0) {
device_printf(sc->dev, "missing sda-gpios property\n");
return (err);
}
}
return (0);
}
#endif
static int
gpioiic_setup_hinted_pins(struct gpioiic_softc *sc)
{
device_t busdev;
const char *busname, *devname;
int err, numpins, sclnum, sdanum, unit;
devname = device_get_name(sc->dev);
unit = device_get_unit(sc->dev);
busdev = device_get_parent(sc->dev);
if (resource_string_value(devname, unit, "at", &busname) != 0 ||
(strcmp(busname, device_get_nameunit(busdev)) != 0 &&
strcmp(busname, device_get_name(busdev)) != 0)) {
return (ENOENT);
}
numpins = gpiobus_get_npins(sc->dev);
if (numpins < GPIOIIC_MIN_PINS) {
#ifdef FDT
if (numpins == 0) {
return (ENOENT);
}
#endif
device_printf(sc->dev,
"invalid pins hint; it must contain at least %d pins\n",
GPIOIIC_MIN_PINS);
return (EINVAL);
}
if ((err = resource_int_value(devname, unit, "scl", &sclnum)) != 0)
sclnum = GPIOIIC_SCL_DFLT;
else if (sclnum < 0 || sclnum >= numpins) {
device_printf(sc->dev, "invalid scl hint %d\n", sclnum);
return (EINVAL);
}
if ((err = resource_int_value(devname, unit, "sda", &sdanum)) != 0)
sdanum = GPIOIIC_SDA_DFLT;
else if (sdanum < 0 || sdanum >= numpins) {
device_printf(sc->dev, "invalid sda hint %d\n", sdanum);
return (EINVAL);
}
if ((err = gpio_pin_get_by_child_index(sc->dev, sclnum,
&sc->sclpin)) != 0)
return (err);
if ((err = gpio_pin_get_by_child_index(sc->dev, sdanum,
&sc->sdapin)) != 0)
return (err);
return (0);
}
static void
gpioiic_setsda(device_t dev, int val)
{
struct gpioiic_softc *sc = device_get_softc(dev);
if (val) {
gpio_pin_setflags(sc->sdapin, GPIO_PIN_INPUT);
} else {
gpio_pin_setflags(sc->sdapin,
GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN);
gpio_pin_set_active(sc->sdapin, 0);
}
}
static void
gpioiic_setscl(device_t dev, int val)
{
struct gpioiic_softc *sc = device_get_softc(dev);
if (val) {
gpio_pin_setflags(sc->sclpin, GPIO_PIN_INPUT);
} else {
gpio_pin_setflags(sc->sclpin,
GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN);
gpio_pin_set_active(sc->sclpin, 0);
}
}
static int
gpioiic_getscl(device_t dev)
{
struct gpioiic_softc *sc = device_get_softc(dev);
bool val;
gpio_pin_setflags(sc->sclpin, GPIO_PIN_INPUT);
gpio_pin_is_active(sc->sclpin, &val);
return (val);
}
static int
gpioiic_getsda(device_t dev)
{
struct gpioiic_softc *sc = device_get_softc(dev);
bool val;
gpio_pin_setflags(sc->sdapin, GPIO_PIN_INPUT);
gpio_pin_is_active(sc->sdapin, &val);
return (val);
}
static int
gpioiic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
{
struct gpioiic_softc *sc = device_get_softc(dev);
gpio_pin_setflags(sc->sdapin, GPIO_PIN_INPUT);
gpio_pin_setflags(sc->sclpin, GPIO_PIN_INPUT);
return (IIC_ENOADDR);
}
static void
gpioiic_cleanup(struct gpioiic_softc *sc)
{
device_delete_children(sc->dev);
if (sc->sclpin != NULL)
gpio_pin_release(sc->sclpin);
if (sc->sdapin != NULL)
gpio_pin_release(sc->sdapin);
}
static int
gpioiic_probe(device_t dev)
{
int rv;
rv = BUS_PROBE_NOWILDCARD;
#ifdef FDT
if (ofw_bus_status_okay(dev) &&
ofw_bus_search_compatible(dev, compat_data)->ocd_data)
rv = BUS_PROBE_DEFAULT;
#endif
device_set_desc(dev, "GPIO I2C");
return (rv);
}
static int
gpioiic_attach(device_t dev)
{
struct gpioiic_softc *sc = device_get_softc(dev);
int err;
sc->dev = dev;
err = gpioiic_setup_hinted_pins(sc);
#ifdef FDT
if (err != 0)
err = gpioiic_setup_fdt_pins(sc);
#endif
if (err != 0) {
device_printf(sc->dev, "no pins configured\n");
gpioiic_cleanup(sc);
return (ENXIO);
}
device_printf(dev, "SCL pin: %s:%d, SDA pin: %s:%d\n",
#ifdef FDT
device_get_nameunit(GPIO_GET_BUS(sc->sclpin->dev)), sc->sclpin->pin,
device_get_nameunit(GPIO_GET_BUS(sc->sdapin->dev)), sc->sdapin->pin);
#else
device_get_nameunit(device_get_parent(dev)), sc->sclpin->pin,
device_get_nameunit(device_get_parent(dev)), sc->sdapin->pin);
#endif
device_add_child(sc->dev, "iicbb", DEVICE_UNIT_ANY);
bus_attach_children(dev);
return (0);
}
static int
gpioiic_detach(device_t dev)
{
struct gpioiic_softc *sc = device_get_softc(dev);
int err;
if ((err = bus_detach_children(dev)) != 0)
return (err);
gpioiic_cleanup(sc);
return (0);
}
static device_method_t gpioiic_methods[] = {
DEVMETHOD(device_probe, gpioiic_probe),
DEVMETHOD(device_attach, gpioiic_attach),
DEVMETHOD(device_detach, gpioiic_detach),
DEVMETHOD(iicbb_setsda, gpioiic_setsda),
DEVMETHOD(iicbb_setscl, gpioiic_setscl),
DEVMETHOD(iicbb_getsda, gpioiic_getsda),
DEVMETHOD(iicbb_getscl, gpioiic_getscl),
DEVMETHOD(iicbb_reset, gpioiic_reset),
#ifdef FDT
DEVMETHOD(ofw_bus_get_node, gpioiic_get_node),
#endif
DEVMETHOD_END
};
static driver_t gpioiic_driver = {
"gpioiic",
gpioiic_methods,
sizeof(struct gpioiic_softc),
};
DRIVER_MODULE(gpioiic, gpiobus, gpioiic_driver, 0, 0);
DRIVER_MODULE(gpioiic, simplebus, gpioiic_driver, 0, 0);
DRIVER_MODULE(iicbb, gpioiic, iicbb_driver, 0, 0);
MODULE_DEPEND(gpioiic, iicbb, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER);
MODULE_DEPEND(gpioiic, gpiobus, 1, 1, 1);