pcf8574_nct.c revision 07d06da50d310a325b457d6330165aebab1e0064
/*
* 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
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/eucioctl.h>
#include <sys/mct_topology.h>
#include <sys/netract_gen.h>
#include <sys/pcf8574_nct.h>
#include <sys/scsb_cbi.h>
#ifdef DEBUG
static int pcf8574_debug = 0x00000102;
#else
#endif
{ \
return (retval); \
} \
} \
}
#define CV_UNLOCK \
{ \
unitp->pcf8574_flags = 0; \
}
static int nct_p10fan_patch = 0; /* Fan patch for P1.0 */
static void *pcf8574_soft_statep;
/*
* cb ops (only need open,close,read,write,ioctl)
*/
static struct cb_ops pcf8574_cbops = {
pcf8574_open, /* open */
pcf8574_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
pcf8574_read, /* read */
nodev, /* write */
pcf8574_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
pcf8574_chpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
NULL, /* streamtab */
CB_REV, /* rev */
nodev, /* int (*cb_aread)() */
nodev /* int (*cb_awrite)() */
};
/*
* dev ops
*/
/* kstat routines */
static void pcf8574_delete_kstat(struct pcf8574_unit *);
static int pcf8574_kstat_update(kstat_t *, int);
/*
* SCSB callback function
*/
fru_id_t);
extern int scsb_intr_unregister(fru_id_t);
static struct dev_ops pcf8574_ops = {
0,
NULL, /* bus_ops */
NULL, /* power */
ddi_quiesce_not_supported, /* devo_quiesce */
};
extern struct mod_ops mod_driverops;
static struct modldrv pcf8574_modldrv = {
&mod_driverops, /* type of module - driver */
"Netract pcf8574 (gpio)",
};
static struct modlinkage pcf8574_modlinkage = {
0
};
int
_init(void)
{
register int error;
if (!error) {
(void) ddi_soft_state_init(&pcf8574_soft_statep,
sizeof (struct pcf8574_unit), PCF8574_MAX_DEVS);
}
return (error);
}
int
_fini(void)
{
register int error;
if (!error)
return (error);
}
int
{
}
/*ARGSUSED*/
static int
{
struct pcf8574_unit *unitp;
register int instance;
int err = DDI_SUCCESS;
if (instance < 0) {
return (ENXIO);
}
unitp = (struct pcf8574_unit *)
return (ENXIO);
}
return (EINVAL);
}
if (unitp->pcf8574_oflag != 0) {
} else {
}
} else {
} else {
}
}
return (err);
}
/*ARGSUSED*/
static int
{
struct pcf8574_unit *unitp;
register int instance;
#ifdef lint
#endif
if (instance < 0) {
return (ENXIO);
}
unitp = (struct pcf8574_unit *)
return (ENXIO);
}
unitp->pcf8574_oflag = 0;
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
}
static int
{
struct pcf8574_unit *unitp;
register int instance;
int err = DDI_SUCCESS;
err = 0;
if (instance < 0) {
return (ENXIO);
}
unitp = (struct pcf8574_unit *)
return (ENXIO);
}
return (EINVAL);
}
if (!err) {
}
} else {
if (!err) {
}
}
if (err)
return (err);
}
static int
{
struct pcf8574_unit *unitp =
return (ENXIO);
}
return (DDI_SUCCESS);
}
static int
{
struct pcf8574_unit *unitp;
int instance;
if (attach_flag & PCF8574_INTR_ADDED) {
(void) scsb_intr_unregister(
}
if (attach_flag & PCF8574_KSTAT_INIT) {
}
if (attach_flag & PCF8574_LOCK_INIT) {
}
scsb_fru_unregister((void *)unitp,
if (attach_flag & PCF8574_ALLOC_TRANSFER) {
/*
* restore the lengths to allocated lengths
* before freeing.
*/
}
if (attach_flag & PCF8574_REGISTER_CLIENT) {
}
if (attach_flag & PCF8574_MINORS_CREATED) {
}
if (attach_flag & PCF8574_PROPS_READ) {
} else {
"interrupt-priorities");
}
}
if (attach_flag & PCF8574_SOFT_STATE_ALLOC) {
}
return (DDI_SUCCESS);
}
/*
* NOTE****
* The OBP will create device tree node for all I2C devices which
* may be present in a system. This means, even if the device is
* not physically present, the device tree node exists. We also
* will succeed the attach routine, since currently there is no
* hotplug support in the I2C bus, and the FRUs need to be hot
* swappable. Only during an I2C transaction we figure out whether
* the particular I2C device is actually present in the system
* by looking at the system controller board register. The fantray
* and power-supply devices may be swapped any time after system
* reboot, and the way we can make sure that the device is attached
* to the driver, is by always keeping the driver loaded, and report
* an error during the actual transaction.
*/
static int
{
register struct pcf8574_unit *unitp;
int instance;
char name[MAXNAMELEN];
int i;
#ifdef DEBUG
if (pcf8574_debug & 0x04)
instance);
#endif /* DEBUG */
DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/*
* Set the current operating mode to NORMAL_MODE.
*/
(void) pcf8574_do_detach(dip);
return (DDI_FAILURE);
}
}
else
(void) pcf8574_do_detach(dip);
return (DDI_FAILURE);
}
}
else
(void) pcf8574_do_detach(dip);
return (DDI_FAILURE);
}
} else {
return (DDI_FAILURE);
}
/*
* If no channels-in-use propoerty, set default values.
*/
}
}
}
#ifdef DEBUG
#endif /* DEBUG */
!= I2C_SUCCESS) {
(void) pcf8574_do_detach(dip);
return (DDI_FAILURE);
}
/*
* Allocate the I2C_transfer structure. The same structure
* is used throughout the driver.
*/
(void) pcf8574_do_detach(dip);
return (DDI_FAILURE);
}
/*
* To begin with we set the mode to I2C_RD.
*/
/*
* Set the busy flag and open flag to 0.
*/
unitp->pcf8574_flags = 0;
unitp->pcf8574_oflag = 0;
/*
* Register out callback function with the SCSB driver, and save
* the returned value to check that the device instance exists.
*/
if (dev_presence == FRU_NOT_AVAILABLE) {
scsb_fru_unregister((void *)unitp,
}
/*
* Add the kstats. First we need to get the property values
* depending on the device type. For example, for the fan
* tray there will be a different set of properties, and there
* will be another for the powersupplies, and another one for
* the CPU voltage monitor. Initialize the kstat structures with
* these values.
*/
(void) pcf8574_do_detach(dip);
return (DDI_FAILURE);
}
/*
* Due to observed behavior on Solaris 8, the handler must be
* registered before any interrupts are enabled,
* in spite of what the ddi_get_iblock_cookie() manual says.
*/
}
#ifdef DEBUG
if (pcf8574_debug & 0x0004)
"handler");
#endif /* DEBUG */
} else {
(void) pcf8574_do_detach(dip);
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
static int
{
switch (cmd) {
case DDI_ATTACH:
return (pcf8574_do_attach(dip));
case DDI_RESUME:
return (pcf8574_do_resume(dip));
default:
return (DDI_FAILURE);
}
}
static int
{
struct pcf8574_unit *unitp =
return (ENXIO);
}
/*
* Set the busy flag so that future transactions block
* until resume.
*/
return (DDI_SUCCESS);
}
static int
{
switch (cmd) {
case DDI_DETACH:
return (pcf8574_do_detach(dip));
case DDI_SUSPEND:
return (pcf8574_do_suspend(dip));
default:
return (DDI_FAILURE);
}
}
static int
{
struct pcf8574_unit *unitp;
int instance;
return (ENXIO);
}
*reventsp = 0;
if (unitp->poll_event) {
unitp->poll_event = 0;
return (0);
}
/*
* In normal scenarios, this function should never get called.
* But, we will still come back and call this function if scsb
* interrupt sources does not indicate an scsb interrupt. We may
* come to this situation when SunVTS env4test is independently
* changing the device registers.
*/
{
int ic;
#ifdef DEBUG
#endif
/*
* Initiate an I2C transaction to find out
* whether this is the device which interrupted.
*/
return (DDI_INTR_UNCLAIMED);
}
}
switch (unitp->pcf8574_type) {
case PCF8574_TYPE_CPUVOLTAGE: {
break;
}
case PCF8574_TYPE_PWRSUPP: {
break;
}
case PCF8574_TYPE_FANTRAY: {
break;
}
}
if (dev_presence != FRU_PRESENT) {
goto intr_exit;
}
goto intr_exit;
}
/*
* If interrupt is already masked, return
*/
if (value & PCF8574_INTRMASK_BIT) {
goto intr_exit;
}
/*
* In case a fault bit is set, claim the interrupt.
*/
switch (unitp->pcf8574_type) {
case PCF8574_TYPE_PWRSUPP:
{
if (PCF8574_PS_FAULT(value) ||
unitp->i2c_status =
} else {
}
}
break;
case PCF8574_TYPE_FANTRAY:
{
if (!PCF8574_FAN_FAULT(value)) {
unitp->i2c_status =
} else {
}
}
break;
default:
} /* switch */
unitp->pcf8574_flags = 0;
return (ic);
}
static int
{
return (I2C_FAILURE);
}
/*
* Save the read and write buffer pointers in the transfer
* structure, otherwise these will get overwritten when we
* do a bcopy. Restore once done.
*/
/*
* copyin the read and write buffers to the saved buffers.
*/
return (I2C_FAILURE);
}
}
return (I2C_SUCCESS);
}
static int
{
/*
* We will copyout the last three fields only, skipping
* the remaining ones, before copying the rbuf to the
* user buffer.
*/
/*
* First copyin the user structure to the temporary i2ct,
* so that we have the wbuf and rbuf addresses in it.
*/
/*
* copyout the last three out fields now.
*/
!= DDI_SUCCESS) {
return (I2C_FAILURE);
}
/*
* In case we have something to write, get the address of the read
* buffer.
*/
return (I2C_FAILURE);
}
/*
* copyout the read buffer to the saved user buffer in i2ct.
*/
!= DDI_SUCCESS) {
return (I2C_FAILURE);
}
}
return (I2C_SUCCESS);
}
/*ARGSUSED*/
static int
{
struct pcf8574_unit *unitp;
register int instance;
int err = 0;
if (instance < 0) {
return (ENXIO);
}
unitp = (struct pcf8574_unit *)
return (ENXIO);
}
switch (cmd) {
case ENVC_IOC_INTRMASK:
if (dev_presence == FRU_NOT_PRESENT) {
break;
}
break;
}
} else {
!= I2C_SUCCESS) {
}
}
break;
case ENVC_IOC_SETFAN:
break;
}
if (dev_presence == FRU_NOT_PRESENT) {
break;
}
mode) != DDI_SUCCESS) {
break;
}
if (inval != PCF8574_FAN_SPEED_LOW &&
inval != PCF8574_FAN_SPEED_HIGH) {
break;
}
!= I2C_SUCCESS) {
}
break;
case ENVC_IOC_SETSTATUS:
/*
* Allow this ioctl only in DIAG mode.
*/
} else {
if (dev_presence == FRU_NOT_PRESENT) {
break;
}
} else {
!= I2C_SUCCESS) {
}
}
}
break;
case ENVC_IOC_GETFAN:
case ENVC_IOC_GETSTATUS:
case ENVC_IOC_GETTYPE:
case ENVC_IOC_GETFAULT:
case ENVC_IOC_PSTEMPOK:
case ENVC_IOC_PSFANOK:
case ENVC_IOC_PSONOFF: {
if (dev_presence == FRU_NOT_PRESENT) {
break;
}
!= I2C_SUCCESS) {
break;
}
if (cmd == ENVC_IOC_GETFAN) {
break;
} else {
}
}
else
if (cmd == ENVC_IOC_GETSTATUS) {
}
else
if (cmd == ENVC_IOC_GETTYPE) {
}
else
if (cmd == ENVC_IOC_GETFAULT) {
}
else
if (cmd == ENVC_IOC_PSTEMPOK) {
}
else
if (cmd == ENVC_IOC_PSFANOK) {
}
else
if (cmd == ENVC_IOC_PSONOFF) {
} else {
outval = 0;
}
}
}
break;
case ENVC_IOC_GETMODE: {
}
break;
}
case ENVC_IOC_SETMODE: {
break;
}
if (curr_mode == ENVCTRL_DIAG_MODE ||
curr_mode == ENVCTRL_NORMAL_MODE) {
}
break;
}
case I2CDEV_TRAN:
break;
}
if (err != I2C_SUCCESS) {
} else {
!= DDI_SUCCESS) {
break;
}
}
break;
default:
}
return (err);
}
static int
{
char ksname[50];
int id;
/*
* We create the kstat depending on the device function,
* allocate the kstat placeholder and initialize the
* values.
*/
switch (unitp->pcf8574_type) {
case PCF8574_TYPE_CPUVOLTAGE:
{
KSTAT_TYPE_RAW, sizeof (envctrl_cpuvoltage_t),
KSTAT_FLAG_PERSISTENT)) != NULL) {
sizeof (envctrl_cpuvoltage_t), KM_NOSLEEP)) ==
NULL) {
return (DDI_FAILURE);
}
} else {
return (DDI_FAILURE);
}
break;
}
case PCF8574_TYPE_PWRSUPP:
{
if (i2c_address == PCF8574_ADR_PWRSUPPLY1) {
id = 1;
} else if (i2c_address == PCF8574_ADR_PWRSUPPLY2) {
id = 2;
} else {
}
KSTAT_TYPE_RAW, sizeof (envctrl_pwrsupp_t),
KSTAT_FLAG_PERSISTENT)) != NULL) {
sizeof (envctrl_pwrsupp_t), KM_NOSLEEP)) ==
NULL) {
return (DDI_FAILURE);
}
/*
* Initialize the kstat fields. Need to initialize
* the present field from SCSB info (dev_presence)
*/
} else {
return (DDI_FAILURE);
}
break;
}
case PCF8574_TYPE_FANTRAY:
{
if (i2c_address == PCF8574_ADR_FANTRAY1) {
id = 1;
} else if (i2c_address == PCF8574_ADR_FANTRAY2) {
id = 2;
} else {
}
KSTAT_TYPE_RAW, sizeof (envctrl_fantray_t),
sizeof (envctrl_fantray_t), KM_NOSLEEP)) ==
NULL) {
return (DDI_FAILURE);
}
/*
* Initialize the kstat fields. Need to initialize
* the present field from SCSB info (dev_presence)
*/
} else {
return (DDI_FAILURE);
}
break;
}
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* This function reads a single byte from the pcf8574 chip, for use by the
* kstat routines. The protocol for read will depend on the function.
*/
static int
{
int retval, i;
/*
* Read the bytes from the pcf8574, mask off the
* non-read bits and return the value. Block with
* the driverwide lock.
*/
if (retval != I2C_SUCCESS) {
return (retval);
}
for (i = 0; i < size; i++) {
}
return (I2C_SUCCESS);
}
/*
* This function writes a single byte to the pcf8574 chip, for use by the
* ioctl routines. The protocol for write will depend on the function.
* The bitpattern tells which bits are being modified, by setting these
* bits in bitpattern to 1, e.g for fanspeed, bitpattern = 0x08, fanspeed
* and intr 0x0c, only intr 0x04.
*/
static int
{
int i;
/*
* pcf8574_write
*
* First read the byte, modify only the writable
* ports, then write back the modified data.
*/
return (I2C_FAILURE);
}
/*
* Our concern is when we have to write only a few bits.
* We need to make sure we write the same value to those
* bit positions which does not appear in bitpattern.
*/
/*
* 1) Ignore all bits than the one we are writing
* 2) Now 0 the bits we intend to modify in the value
* read from the chip, preserving all others.
* bits to 1. The value now should contain:
* 1 in all non-writable bits.
* 0 in the bis(s) we intend to modify.
* no change in the writable bits we don't modify.
* 4) Now OR it with the bits we got before, i.e. after
* ignoring all bits other than one we are writing.
*/
for (i = 0; i < size; i++) {
}
return (unitp->i2c_status);
}
static int
{
struct pcf8574_unit *unitp;
char *kstatp;
int err = DDI_SUCCESS;
return (err);
}
/*
* Need to call scsb to find whether device is present.
* For I2C devices, the I2C address is used as a FRU ID.
*/
} else {
}
/*
* We could have write on the power supply and the fantray
* pcf8574 chips. For masking the interrupt on both, or
* controlling the fan speed on the fantray. But write
* will not be allowed through the kstat interface. For
* the present field, call SCSB.
*/
if (rw == KSTAT_WRITE) {
goto kstat_exit;
}
if (value != PCF8574_FAN_SPEED_LOW &&
value != PCF8574_FAN_SPEED_HIGH) {
goto kstat_exit;
}
if (dev_presence == FRU_PRESENT &&
!= I2C_SUCCESS) {
goto kstat_exit;
}
} else {
/*
* First make sure that the FRU exists by checking the SCSB
* dev_presence info. If not present, set the change field,
* clear the kstat fields and make sure the kstat *_present
* field is set to dev_presence from the SCSB driver.
*/
if (dev_presence == FRU_PRESENT &&
/*
* Looks like a real IO error.
*/
return (err);
}
if (dev_presence == FRU_PRESENT)
else
value = 0;
switch (unitp->pcf8574_type) {
case PCF8574_TYPE_CPUVOLTAGE: {
sizeof (envctrl_cpuvoltage_t));
break;
}
case PCF8574_TYPE_PWRSUPP: {
sizeof (envctrl_pwrsupp_t));
break;
}
case PCF8574_TYPE_FANTRAY: {
sizeof (envctrl_fantray_t));
break;
}
default:
break;
}
}
return (err);
}
static void
{
/*
* Depending on the function, deallocate the correct
* kernel allocated memory.
*/
}
switch (unitp->pcf8574_type) {
case PCF8574_TYPE_CPUVOLTAGE: {
sizeof (envctrl_cpuvoltage_t));
}
break;
}
case PCF8574_TYPE_PWRSUPP: {
sizeof (envctrl_pwrsupp_t));
}
break;
}
case PCF8574_TYPE_FANTRAY: {
sizeof (envctrl_fantray_t));
}
break;
}
default:
break;
}
}
static int
{
char *function;
/*
* read the pcf8574_function property. If this property is not
* found, return ERROR. Else, make sure it's either powersupply
* or fantray.
*/
return (DDI_FAILURE);
}
/*
* Will fail the fantray attach if patch - 1.
*/
if (nct_p10fan_patch) {
#ifdef DEBUG
#endif
return (DDI_FAILURE);
}
} else
} else {
return (DDI_FAILURE);
}
if (retval == DDI_PROP_SUCCESS) {
else {
}
} else {
}
/*
* Get the Property information that the driver will be using
* see typedef struct pcf8574_properties_t;
*/
unitp->pcf8574_canintr = 0;
"interrupts", -1);
if (retval >= 0) {
DDI_PROP_DONTPASS, "interrupt-priorities",
&prop_len) == DDI_PROP_NOT_FOUND) {
DDI_PROP_CANSLEEP, "interrupt-priorities",
#ifdef DEBUG
if (retval != DDI_PROP_SUCCESS) {
priorities property, retval %d", retval);
}
#endif /* DEBUG */
}
}
/*
* No channels-in-use property for the fan and powersupplies.
*/
if (i2c_address == PCF8574_ADR_CPUVOLTAGE) {
"channels-in-use",
if (retval != DDI_PROP_SUCCESS) {
} else {
sizeof (pcf8574_channel_t);
}
}
}
return (DDI_PROP_SUCCESS);
}
/*
* callback function to register with the SCSB driver in order to be
* informed about changes in device instance presence.
*/
/*ARGSUSED*/
void
{
#ifdef DEBUG
if (pcf8574_debug & 0x00800001)
(int)cb_event, (int)dev_presence);
#endif /* DEBUG */
switch (unitp->pcf8574_type) {
case PCF8574_TYPE_CPUVOLTAGE: {
/*
* This Unit is not Field Replacable and will not
* generate any events at the SCB.
*/
break;
}
case PCF8574_TYPE_PWRSUPP: {
if (dev_presence == FRU_NOT_PRESENT) {
} else
if (dev_presence == FRU_PRESENT &&
(void) pcf8574_init_chip(unitp, 0);
}
break;
}
case PCF8574_TYPE_FANTRAY: {
if (dev_presence == FRU_NOT_PRESENT) {
} else
if (dev_presence == FRU_PRESENT &&
(void) pcf8574_init_chip(unitp, 0);
}
break;
}
}
}
/*
* Initializes the chip after attach or after being inserted.
* intron = 0 => disable interrupt.
* intron = 1 => read register, enable interrupt if no fault.
*/
static int
{
int ret = I2C_SUCCESS;
return (ret);
}
switch (unitp->pcf8574_type) {
case PCF8574_TYPE_PWRSUPP:
break;
case PCF8574_TYPE_FANTRAY:
break;
default:
break;
}
/*
* First, read the device. If the device is faulty, it does
* not make sense to enable the interrupt, so in this case
* keep interrupt maskked inspite of what "intron" says.
*/
if (ret != I2C_SUCCESS) {
return (ret);
}
switch (unitp->pcf8574_type) {
case PCF8574_TYPE_PWRSUPP:
{
break;
}
case PCF8574_TYPE_FANTRAY:
{
break;
}
default:
break;
}
/*
* Mask interrupt, if intron = 0.
*/
}
return (unitp->i2c_status);
}