/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include <sys/types.h>
#include <sys/kmem.h>
#include <sys/systm.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/conf.h>
#include <sys/mode.h>
#include <sys/note.h>
#include <sys/i2c/clients/i2c_gpio.h>
#include <sys/i2c/clients/pca9556_impl.h>
/*
* The PCA9556 is a gpio chip with 8 I/O ports. The ports may be controlled by
* an 8 bit input port register, 8 bit output port register, 8 bit polarity
* inversion register and an 8 bit configuration register.
*
* The input port register is a read only port and writes to this register
* will have no effect regardless of whether the pin is an input or output.
*
* The output port register reflects the outgoing logic levels of the pins
* defined as outputs by the configuration register. Bit values in this
* register have no effect on pins defined as inputs.
*
* The polarity register enables polarity inversion of pins defined as inputs by
* the configuration register. A set bit inverts the corresponding port's
* polarity.
*
* The configuration register configures the directions of the I/O pins. If a
* bit is set the corresponding port is enabled as an input and if cleared,
* as an output.
*
* The commands supported in the ioctl routine are:
* GPIO_GET_INPUT -- Read bits in the input port register.
* GPIO_GET_OUTPUT -- Read bits in the output port register.
* GPIO_SET_OUPUT -- Modify bits in the output port register.
* GPIO_GET_POLARITY -- Read bits in the polarity register.
* GPIO_SET_POLARITY -- Modify bits in the polarity register.
* GPIO_GET_CONFIG -- Read bits in the configuration register.
* GPIO_SET_CONFIG -- Modify bits in the configuration register.
*
* A pointer to the i2c_gpio_t data structure is sent as the third argument
* in the ioctl call. The reg_mask member identifies the bits that the user
* wants to read or modify and reg_val has the actual value of the
* corresponding bits set in reg_mask.
*
* To read a whole register the user has to set all the bits in reg_mask
* and the values will be copied into reg_val.
*
* In addition the pca9555 device has been added to this driver. It is similar
* to the pca9556 except that it has 2 8 bit I/O ports.
*/
/*
* cb ops
*/
static int pca9556_open(dev_t *, int, int, cred_t *);
static int pca9556_close(dev_t, int, int, cred_t *);
static int pca9556_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
/*
* dev ops
*/
static int pca9556_s_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int pca9556_s_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int pca9556_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static struct cb_ops pca9556_cb_ops = {
pca9556_open, /* open */
pca9556_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
pca9556_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
NULL, /* streamtab */
D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */
};
static struct dev_ops pca9556_dev_ops = {
DEVO_REV,
0,
pca9556_info,
nulldev,
nulldev,
pca9556_s_attach,
pca9556_s_detach,
nodev,
&pca9556_cb_ops,
NULL,
NULL,
ddi_quiesce_not_supported, /* devo_quiesce */
};
static struct modldrv pca9556_modldrv = {
&mod_driverops, /* type of module - driver */
"pca9556 device driver",
&pca9556_dev_ops,
};
static struct modlinkage pca9556_modlinkage = {
MODREV_1,
&pca9556_modldrv,
0
};
static void *pca9556_soft_statep;
int pca9556_debug;
int
_init(void)
{
int err;
err = mod_install(&pca9556_modlinkage);
if (err == 0) {
(void) ddi_soft_state_init(&pca9556_soft_statep,
sizeof (pca9556_unit_t), PCA9556_MAX_SIZE);
}
return (err);
}
int
_fini(void)
{
int err;
err = mod_remove(&pca9556_modlinkage);
if (err == 0) {
ddi_soft_state_fini(&pca9556_soft_statep);
}
return (err);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&pca9556_modlinkage, modinfop));
}
static int
pca9556_resume(dev_info_t *dip)
{
int instance = ddi_get_instance(dip);
pca9556_unit_t *pcap;
int err = DDI_SUCCESS;
int reg_offset, num_of_ports;
int i, j;
uint8_t reg, reg_num = 0;
extern int do_polled_io;
int saved_pio;
pcap = (pca9556_unit_t *)
ddi_get_soft_state(pca9556_soft_statep, instance);
if (pcap == NULL)
return (ENXIO);
/*
* Restore registers to status existing before cpr
*/
pcap->pca9556_transfer->i2c_flags = I2C_WR;
pcap->pca9556_transfer->i2c_wlen = 2;
pcap->pca9556_transfer->i2c_rlen = 0;
if (pcap->pca9555_device) {
reg_offset = 2;
num_of_ports = PCA9555_NUM_PORTS;
} else {
reg_offset = 1;
num_of_ports = PCA9556_NUM_PORTS;
}
/*
* Since the parent node that handles interrupts may have already
* been suspended, perform the following i2c transfers in poll-mode.
*/
saved_pio = do_polled_io;
do_polled_io = 1;
for (i = 0; i < num_of_ports; i++) {
if (pcap->pca9555_device)
reg = PCA9555_OUTPUT_REG;
else
reg = PCA9556_OUTPUT_REG;
for (j = 0; j < PCA9556_NUM_REG; j++) {
pcap->pca9556_transfer->i2c_wbuf[0] = reg + i;
pcap->pca9556_transfer->i2c_wbuf[1] =
pcap->pca9556_cpr_state[reg_num++];
if (i2c_transfer(pcap->pca9556_hdl,
pcap->pca9556_transfer) != DDI_SUCCESS) {
err = EIO;
goto done;
}
reg = reg + reg_offset;
}
}
done:
do_polled_io = saved_pio;
if (err != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s Unable to restore registers",
pcap->pca9556_name);
}
/*
* Clear busy flag so that transactions may continue
*/
mutex_enter(&pcap->pca9556_mutex);
pcap->pca9556_flags = pcap->pca9556_flags & ~PCA9556_BUSYFLAG;
cv_broadcast(&pcap->pca9556_cv);
mutex_exit(&pcap->pca9556_mutex);
return (err);
}
static void
pca9556_detach(dev_info_t *dip)
{
pca9556_unit_t *pcap;
int instance = ddi_get_instance(dip);
pcap = ddi_get_soft_state(pca9556_soft_statep, instance);
if ((pcap->pca9556_flags & PCA9556_REGFLAG) == PCA9556_REGFLAG) {
i2c_client_unregister(pcap->pca9556_hdl);
}
if ((pcap->pca9556_flags & PCA9556_TBUFFLAG) == PCA9556_TBUFFLAG) {
i2c_transfer_free(pcap->pca9556_hdl, pcap->pca9556_transfer);
}
if ((pcap->pca9556_flags & PCA9556_MINORFLAG) == PCA9556_MINORFLAG) {
ddi_remove_minor_node(dip, NULL);
}
cv_destroy(&pcap->pca9556_cv);
mutex_destroy(&pcap->pca9556_mutex);
ddi_soft_state_free(pca9556_soft_statep, instance);
}
static int
pca9556_attach(dev_info_t *dip)
{
pca9556_unit_t *pcap;
int instance = ddi_get_instance(dip);
char name[MAXNAMELEN];
char *device_name;
minor_t minor;
int i, num_ports;
if (ddi_soft_state_zalloc(pca9556_soft_statep, instance) != 0) {
cmn_err(CE_WARN, "%s%d failed to zalloc softstate",
ddi_get_name(dip), instance);
return (DDI_FAILURE);
}
pcap = ddi_get_soft_state(pca9556_soft_statep, instance);
if (pcap == NULL)
return (DDI_FAILURE);
mutex_init(&pcap->pca9556_mutex, NULL, MUTEX_DRIVER, NULL);
cv_init(&pcap->pca9556_cv, NULL, CV_DRIVER, NULL);
(void) snprintf(pcap->pca9556_name, sizeof (pcap->pca9556_name),
"%s_%d", ddi_driver_name(dip), instance);
device_name = ddi_get_name(dip);
if (strcmp(device_name, "i2c-pca9555") == 0) {
num_ports = PCA9555_NUM_PORTS;
pcap->pca9555_device = B_TRUE;
} else {
num_ports = PCA9556_NUM_PORTS;
pcap->pca9555_device = B_FALSE;
minor = INST_TO_MINOR(instance);
}
for (i = 0; i < num_ports; i++) {
if (!(pcap->pca9555_device)) {
(void) snprintf(pcap->pca9556_name,
sizeof (pcap->pca9556_name), "%s_%d",
ddi_driver_name(dip), instance);
(void) snprintf(name, sizeof (name), "%s",
pcap->pca9556_name);
} else {
(void) sprintf(name, "port_%d", i);
minor = INST_TO_MINOR(instance) |
PORT_TO_MINOR(I2C_PORT(i));
}
if (ddi_create_minor_node(dip, name, S_IFCHR, minor,
PCA9556_NODE_TYPE, NULL) == DDI_FAILURE) {
cmn_err(CE_WARN, "%s: failed to create node for %s",
pcap->pca9556_name, name);
pca9556_detach(dip);
return (DDI_FAILURE);
}
}
pcap->pca9556_flags |= PCA9556_MINORFLAG;
/*
* Add a zero-length attribute to tell the world we support
* kernel ioctls (for layered drivers)
*/
(void) ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
DDI_KERNEL_IOCTL, NULL, 0);
/*
* preallocate a single buffer for all reads and writes
*/
if (i2c_transfer_alloc(pcap->pca9556_hdl, &pcap->pca9556_transfer,
2, 2, I2C_SLEEP) != I2C_SUCCESS) {
cmn_err(CE_WARN, "%s i2c_transfer_alloc failed",
pcap->pca9556_name);
pca9556_detach(dip);
return (DDI_FAILURE);
}
pcap->pca9556_flags |= PCA9556_TBUFFLAG;
pcap->pca9556_transfer->i2c_version = I2C_XFER_REV;
if (i2c_client_register(dip, &pcap->pca9556_hdl) != I2C_SUCCESS) {
ddi_remove_minor_node(dip, NULL);
cmn_err(CE_WARN, "%s i2c_client_register failed",
pcap->pca9556_name);
pca9556_detach(dip);
return (DDI_FAILURE);
}
pcap->pca9556_flags |= PCA9556_REGFLAG;
/*
* Store the dip for future dip.
*/
pcap->pca9556_dip = dip;
return (DDI_SUCCESS);
}
static int
pca9556_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
{
_NOTE(ARGUNUSED(dip))
pca9556_unit_t *pcap;
int instance = MINOR_TO_INST(getminor((dev_t)arg));
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
pcap = ddi_get_soft_state(pca9556_soft_statep, instance);
if (pcap == NULL)
return (DDI_FAILURE);
*result = (void *)pcap->pca9556_dip;
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)instance;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
pca9556_s_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
switch (cmd) {
case DDI_ATTACH:
return (pca9556_attach(dip));
case DDI_RESUME:
return (pca9556_resume(dip));
default:
return (DDI_FAILURE);
}
}
static int
pca9556_suspend(dev_info_t *dip)
{
pca9556_unit_t *pcap;
int instance = ddi_get_instance(dip);
int err = DDI_SUCCESS;
int reg_offset, num_of_ports;
int i, j;
uint8_t reg, reg_num = 0;
extern int do_polled_io;
int saved_pio;
pcap = ddi_get_soft_state(pca9556_soft_statep, instance);
mutex_enter(&pcap->pca9556_mutex);
while ((pcap->pca9556_flags & PCA9556_BUSYFLAG) == PCA9556_BUSYFLAG) {
if (cv_wait_sig(&pcap->pca9556_cv,
&pcap->pca9556_mutex) <= 0) {
mutex_exit(&pcap->pca9556_mutex);
return (DDI_FAILURE);
}
}
pcap->pca9556_flags |= PCA9556_BUSYFLAG;
mutex_exit(&pcap->pca9556_mutex);
/*
* A pca9555 devices command registers are offset by 2 and it has 2
* ports to save. A pca9556 devices command registers are offset by 1
* while it only has one "port"
*/
if (pcap->pca9555_device) {
reg_offset = 2;
num_of_ports = PCA9555_NUM_PORTS;
} else {
reg_offset = 1;
num_of_ports = PCA9556_NUM_PORTS;
}
/*
* Save the state of the registers
*/
pcap->pca9556_transfer->i2c_flags = I2C_WR_RD;
pcap->pca9556_transfer->i2c_wlen = 1;
pcap->pca9556_transfer->i2c_rlen = 1;
/*
* Since the parent node that handles interrupts may have not been
* resumed yet, perform the following i2c transfers in poll-mode.
*/
saved_pio = do_polled_io;
do_polled_io = 1;
/*
* The following for loop will run through once for a pca9556 device
* and twice for a pca9555 device. i will represent the port number
* for the pca9555.
*/
for (i = 0; i < num_of_ports; i++) {
/*
* We set the first Register here so it can be reset if we
* loop through (pca9555 device).
*/
if (pcap->pca9555_device)
reg = PCA9555_OUTPUT_REG;
else
reg = PCA9556_OUTPUT_REG;
/* We run through this loop 3 times. Once for each register */
for (j = 0; j < PCA9556_NUM_REG; j++) {
/*
* We add the port number (0 for pca9556, 0 or 1 for
* a pca9555) to the register.
*/
pcap->pca9556_transfer->i2c_wbuf[0] = reg + i;
if (i2c_transfer(pcap->pca9556_hdl,
pcap->pca9556_transfer) != DDI_SUCCESS) {
err = EIO;
goto done;
}
pcap->pca9556_cpr_state[reg_num++] =
pcap->pca9556_transfer->i2c_rbuf[0];
/*
* The register is then added to the offset and saved
* to go and read the next command register.
*/
reg = reg + reg_offset;
}
}
done:
do_polled_io = saved_pio;
if (err != DDI_SUCCESS) {
mutex_enter(&pcap->pca9556_mutex);
pcap->pca9556_flags = pcap->pca9556_flags & ~PCA9556_BUSYFLAG;
cv_broadcast(&pcap->pca9556_cv);
mutex_exit(&pcap->pca9556_mutex);
cmn_err(CE_WARN, "%s Suspend failed, unable to save registers",
pcap->pca9556_name);
return (err);
}
return (DDI_SUCCESS);
}
static int
pca9556_s_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
switch (cmd) {
case DDI_DETACH:
pca9556_detach(dip);
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (pca9556_suspend(dip));
default:
return (DDI_FAILURE);
}
}
static int
pca9556_open(dev_t *devp, int flags, int otyp, cred_t *credp)
{
int instance;
pca9556_unit_t *pcap;
int err = EBUSY;
/*
* Make sure the open is for the right file type
*/
if (otyp != OTYP_CHR)
return (EINVAL);
instance = MINOR_TO_INST(getminor(*devp));
pcap = (pca9556_unit_t *)
ddi_get_soft_state(pca9556_soft_statep, instance);
if (pcap == NULL)
return (ENXIO);
/* must be privileged to access this device */
if (drv_priv(credp) != 0)
return (EPERM);
/*
* Enforce exclusive access if required
*/
mutex_enter(&pcap->pca9556_mutex);
if (flags & FEXCL) {
if (pcap->pca9556_oflag == 0) {
pcap->pca9556_oflag = FEXCL;
err = DDI_SUCCESS;
}
} else if (pcap->pca9556_oflag != FEXCL) {
pcap->pca9556_oflag = (uint16_t)FOPEN;
err = DDI_SUCCESS;
}
mutex_exit(&pcap->pca9556_mutex);
return (err);
}
static int
pca9556_close(dev_t dev, int flags, int otyp, cred_t *credp)
{
int instance;
pca9556_unit_t *pcap;
_NOTE(ARGUNUSED(flags, credp))
/*
* Make sure the close is for the right file type
*/
if (otyp != OTYP_CHR)
return (EINVAL);
instance = MINOR_TO_INST(getminor(dev));
pcap = (pca9556_unit_t *)
ddi_get_soft_state(pca9556_soft_statep, instance);
if (pcap == NULL)
return (ENXIO);
mutex_enter(&pcap->pca9556_mutex);
pcap->pca9556_oflag = 0;
mutex_exit(&pcap->pca9556_mutex);
return (0);
}
static int
pca9556_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
int *rvalp)
{
pca9556_unit_t *pcap;
int err = 0;
int instance = MINOR_TO_INST(getminor(dev));
int port;
i2c_gpio_t g_buf;
uchar_t temp;
boolean_t write_io = B_FALSE;
_NOTE(ARGUNUSED(credp, rvalp))
pcap = (pca9556_unit_t *)
ddi_get_soft_state(pca9556_soft_statep, instance);
if (pcap->pca9555_device) {
port = MINOR_TO_PORT(getminor(dev));
}
if (pca9556_debug) {
prom_printf("pca9556_ioctl: instance=%d\n", instance);
}
/*
* We serialize here and block any pending transacations.
*/
mutex_enter(&pcap->pca9556_mutex);
while ((pcap->pca9556_flags & PCA9556_BUSYFLAG) == PCA9556_BUSYFLAG) {
if (cv_wait_sig(&pcap->pca9556_cv,
&pcap->pca9556_mutex) <= 0) {
mutex_exit(&pcap->pca9556_mutex);
return (EINTR);
}
}
pcap->pca9556_flags |= PCA9556_BUSYFLAG;
mutex_exit(&pcap->pca9556_mutex);
if (ddi_copyin((caddr_t)arg, &g_buf,
sizeof (i2c_gpio_t), mode) != DDI_SUCCESS) {
err = EFAULT;
goto cleanup;
}
pcap->pca9556_transfer->i2c_flags = I2C_WR_RD;
pcap->pca9556_transfer->i2c_wlen = 1;
pcap->pca9556_transfer->i2c_rlen = 1;
/*
* Evaluate which register is to be read or modified
*/
switch (cmd) {
case GPIO_GET_INPUT:
if (pcap->pca9555_device)
pcap->pca9556_transfer->i2c_wbuf[0] =
PCA9555_INPUT_REG + port;
else
pcap->pca9556_transfer->i2c_wbuf[0] =
PCA9556_INPUT_REG;
break;
case GPIO_SET_OUTPUT:
write_io = B_TRUE;
/*FALLTHROUGH*/
case GPIO_GET_OUTPUT:
if (pcap->pca9555_device)
pcap->pca9556_transfer->i2c_wbuf[0] =
PCA9555_OUTPUT_REG + port;
else
pcap->pca9556_transfer->i2c_wbuf[0] =
PCA9556_OUTPUT_REG;
break;
case GPIO_SET_POLARITY:
write_io = B_TRUE;
/*FALLTHROUGH*/
case GPIO_GET_POLARITY:
if (pcap->pca9555_device)
pcap->pca9556_transfer->i2c_wbuf[0] =
PCA9555_POLARITY_REG + port;
else
pcap->pca9556_transfer->i2c_wbuf[0] =
PCA9556_POLARITY_REG;
break;
case GPIO_SET_CONFIG:
write_io = B_TRUE;
/*FALLTHROUGH*/
case GPIO_GET_CONFIG:
if (pcap->pca9555_device)
pcap->pca9556_transfer->i2c_wbuf[0] =
PCA9555_CONFIG_REG + port;
else
pcap->pca9556_transfer->i2c_wbuf[0] =
PCA9556_CONFIG_REG;
break;
}
/*
* Read the required register
*/
if (i2c_transfer(pcap->pca9556_hdl, pcap->pca9556_transfer)
!= I2C_SUCCESS) {
err = EIO;
goto cleanup;
}
/*
* Evaluate whether the register is to be read or modified
*/
if (!write_io) {
g_buf.reg_val = g_buf.reg_mask &
pcap->pca9556_transfer->i2c_rbuf[0];
err = ddi_copyout(&g_buf, (caddr_t)arg,
sizeof (i2c_gpio_t), mode);
} else {
pcap->pca9556_transfer->i2c_flags = I2C_WR;
pcap->pca9556_transfer->i2c_wlen = 2;
pcap->pca9556_transfer->i2c_rlen = 0;
/*
* Modify register without overwriting existing contents
*/
temp = pcap->pca9556_transfer->i2c_rbuf[0] & (~g_buf.reg_mask);
pcap->pca9556_transfer->i2c_wbuf[1] = temp|
(g_buf.reg_val & g_buf.reg_mask);
if (i2c_transfer(pcap->pca9556_hdl, pcap->pca9556_transfer)
!= I2C_SUCCESS) {
err = EIO;
}
}
cleanup:
mutex_enter(&pcap->pca9556_mutex);
pcap->pca9556_flags = pcap->pca9556_flags & ~PCA9556_BUSYFLAG;
cv_signal(&pcap->pca9556_cv);
mutex_exit(&pcap->pca9556_mutex);
return (err);
}