#include <sys/types.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/stddef.h>
#include <sys/fs/dv_node.h>
#include "i2cnex.h"
i2c_dev_t *
i2c_device_find_by_addr(i2c_txn_t *txn, i2c_port_t *port,
const i2c_addr_t *addr)
{
i2c_dev_t d;
VERIFY(i2c_txn_held(txn));
VERIFY3P(txn->txn_ctrl, ==, port->ip_nex->in_ctrl);
(void) memset(&d, 0, sizeof (i2c_dev_t));
d.id_addr = *addr;
return (avl_find(&port->ip_devices, &d, NULL));
}
static void
i2c_device_free(i2c_dev_t *dev)
{
VERIFY3P(dev->id_mux, ==, NULL);
if (dev->id_nucompat > 0) {
VERIFY3P(dev->id_ucompat, !=, NULL);
for (uint_t i = 0; i < dev->id_nucompat; i++) {
strfree(dev->id_ucompat[i]);
}
kmem_free(dev->id_ucompat, sizeof (char *) * dev->id_nucompat);
}
i2cnex_nex_free(dev->id_nex);
list_destroy(&dev->id_clients);
kmem_free(dev, sizeof (i2c_dev_t));
}
static bool
i2c_device_parent_rm(i2c_port_t *port, void *arg)
{
VERIFY3U(port->ip_ndevs_ds, >, 0);
port->ip_ndevs_ds--;
return (true);
}
static bool
i2c_device_parent_add(i2c_port_t *port, void *arg)
{
port->ip_ndevs_ds++;
return (true);
}
void
i2c_device_fini(i2c_txn_t *txn, i2c_port_t *port, i2c_dev_t *dev)
{
VERIFY(i2c_txn_held(txn));
VERIFY3P(txn->txn_ctrl, ==, port->ip_nex->in_ctrl);
VERIFY3P(dev->id_nex->in_pnex, ==, port->ip_nex);
i2c_port_parent_iter(port, i2c_device_parent_rm, NULL);
avl_remove(&port->ip_devices, dev);
i2c_addr_free(port, &dev->id_addr);
i2c_device_free(dev);
}
i2c_dev_t *
i2c_device_init(i2c_txn_t *txn, i2c_port_t *port, const i2c_addr_t *addr,
const char *name, char *const *compat, uint_t ncompat, i2c_error_t *err)
{
char ua[I2C_NAME_MAX];
i2c_dev_t *dev;
i2c_ctrl_t *ctrl = port->ip_nex->in_ctrl;
VERIFY(i2c_txn_held(txn));
VERIFY3P(txn->txn_ctrl, ==, ctrl);
if (!i2c_addr_alloc(port, addr, err)) {
return (NULL);
}
dev = kmem_zalloc(sizeof (i2c_dev_t), KM_SLEEP);
list_create(&dev->id_clients, sizeof (i2c_client_t),
offsetof(i2c_client_t, icli_dev_link));
dev->id_addr = *addr;
if (ncompat > 0) {
dev->id_nucompat = ncompat;
dev->id_ucompat = kmem_alloc(sizeof (char *) * ncompat,
KM_SLEEP);
for (uint_t i = 0; i < ncompat; i++) {
dev->id_ucompat[i] = strdup(compat[i]);
}
}
(void) snprintf(ua, sizeof (ua), "%x,%x", addr->ia_type, addr->ia_addr);
dev->id_nex = i2cnex_nex_alloc(I2C_NEXUS_T_DEV, port->ip_nex->in_dip,
port->ip_nex, name, ua, ctrl);
if (dev->id_nex == NULL) {
i2c_addr_free(port, &dev->id_addr);
i2c_device_free(dev);
return (NULL);
}
dev->id_nex->in_data.in_dev = dev;
avl_add(&port->ip_devices, dev);
i2c_port_parent_iter(port, i2c_device_parent_add, NULL);
return (dev);
}
bool
i2c_device_unconfig(i2c_port_t *port, i2c_dev_t *dev)
{
int ret;
VERIFY(DEVI_BUSY_OWNED(port->ip_nex->in_dip));
if (dev->id_nex->in_dip == NULL) {
return (true);
}
if (i_ddi_node_state(dev->id_nex->in_dip) < DS_INITIALIZED) {
i2c_nex_dev_cleanup(dev->id_nex);
ret = ddi_remove_child(dev->id_nex->in_dip, 0);
if (ret == NDI_SUCCESS) {
dev->id_nex->in_dip = NULL;
}
} else {
char ua[I2C_NAME_MAX * 4];
(void) snprintf(ua, sizeof (ua), "%s@%s", dev->id_nex->in_name,
dev->id_nex->in_addr);
(void) devfs_clean(port->ip_nex->in_dip, ua, DV_CLEAN_FORCE);
ret = ndi_devi_unconfig_one(port->ip_nex->in_dip, ua, NULL,
NDI_DEVI_REMOVE | NDI_UNCONFIG);
}
if (ret != NDI_SUCCESS) {
return (false);
}
return (true);
}
bool
i2c_device_config(i2c_port_t *port, i2c_dev_t *dev)
{
int ret;
char ua[I2C_NAME_MAX * 4];
dev_info_t *child;
VERIFY(DEVI_BUSY_OWNED(port->ip_nex->in_dip));
(void) snprintf(ua, sizeof (ua), "%s@%s", dev->id_nex->in_name,
dev->id_nex->in_addr);
ret = ndi_devi_config_one(port->ip_nex->in_dip, ua, &child, NDI_CONFIG |
NDI_ONLINE_ATTACH);
if (ret == NDI_SUCCESS) {
ndi_rele_devi(child);
}
return (dev->id_nex->in_dip != NULL);
}