/*
* 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.
* Copyright (c) 2011 Bayard G. Bell. All rights reserved.
*/
/*
* I2C leaf driver for the PCF8591
*/
#include <sys/eucioctl.h>
#include <sys/envctrl_gen.h>
#include <sys/netract_gen.h>
#include <sys/pcf8591_nct.h>
/*
* CONTROL OF CHIP
* PCF8591 Temp sensing control register definitions
*
* ---------------------------------------------
* | 0 | AOE | X | X | 0 | AIF | X | X |
* ---------------------------------------------
* AOE = Analog out enable.. not used on out implementation
* 5 & 4 = Analog Input Programming.. see data sheet for bits..
*
* AIF = Auto increment flag
* bits 1 & 0 are for the Chennel number.
*/
#define I2CTRANS_DATA 0
#ifdef DEBUG
#else
#endif
static void *pcf8591_soft_statep;
/*
* cb ops (only need ioctl)
*/
pcf8591_open, /* open */
pcf8591_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
pcf8591_read, /* read */
nodev, /* write */
pcf8591_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
NULL, /* streamtab */
CB_REV, /* rev */
nodev, /* int (*cb_aread)() */
nodev /* int (*cb_awrite)() */
};
/*
* dev ops
*/
void **result);
/* kstat routines */
static int pcf8591_add_kstats(struct pcf8591_unit *);
static void pcf8591_delete_kstats(struct pcf8591_unit *);
static int pcf8591_temp_kstat_update(kstat_t *, int);
0,
NULL,
NULL,
ddi_quiesce_not_supported, /* devo_quiesce */
};
extern struct mod_ops mod_driverops;
&mod_driverops, /* type of module - driver */
"Netract pcf8591 (adio)",
};
0
};
int
_init(void)
{
register int error;
if (error == 0) {
(void) ddi_soft_state_init(&pcf8591_soft_statep,
sizeof (struct pcf8591_unit), PCF8591_MAX_DEVS);
}
return (error);
}
int
_fini(void)
{
register int error;
if (error == 0) {
}
return (error);
}
int
{
}
/*ARGSUSED*/
static int
{
int err = 0;
if (instance < 0) {
return (ENXIO);
}
unitp = (struct pcf8591_unit *)
return (ENXIO);
}
return (EINVAL);
}
} else {
}
} else {
} else {
}
}
return (err);
}
/*ARGSUSED*/
static int
{
#ifdef lint
#endif
if (instance < 0) {
return (ENXIO);
}
unitp = (struct pcf8591_unit *)
return (ENXIO);
}
return (DDI_SUCCESS);
}
static int
{
int err = 0;
int bytes_to_rw;
int translate = 0;
/*
* At this point we don't have a write operation to pcf8591.
*/
return (EACCES);
}
if (instance < 0) {
return (ENXIO);
}
unitp = (struct pcf8591_unit *)
return (ENXIO);
}
return (EINVAL);
}
/*
* Need to serialize all read operations, since there is a single
* i2c_transfer_t structure allocated for all read and write ops.
* We can't share the i2c bus among multiple transactions anyway,
* so this does not affect performance.
*/
return (EINTR);
}
}
if (bytes_to_rw == 1)
translate = 1;
/*
* Event sequence:
* 1. set up the control register write, for now we'll always read
* channel 0, which is the only active 8591 port on the Nordica
* TODO: We'll need a minor node for each port that is used.
* 2. increment read count to read the throw-away byte
* 4. throw the first byte away
* 5. then return the data
*/
channel);
/*
* read extra byte to throw away the first, (PCF8591 datasheet)
*/
} else {
/*
* Throw away the first byte according to PCF8591 datasheet
* If translating, use the second byte.
*/
if (translate) {
} else {
}
uiop);
}
unitp->pcf8591_flags = 0;
return (err);
}
/*ARGSUSED*/
static int
{
}
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.
*/
return (I2C_FAILURE);
}
}
return (I2C_SUCCESS);
}
/*
* The ioctls will use the same name as the Javelin ioctls. We
* will have a very restricted set for MC, and unlike Javelin
* will not have a envctrl_chip structure to return values
* from the driver. All we will have is a uint8_t value to
* get or set values from the driver. Also, unlike the Javelin,
* where 'index' is used to specify the input port from where
* temperature is collected, here different minor nodes will be
* created by the driver for each port, eliminating the need for
* 'index' - leaving us with only the value to pass.
*/
/*ARGSUSED*/
static int
{
int err = 0;
unitp = (struct pcf8591_unit *)
return (EINTR);
}
}
switch (cmd) {
case ENVC_IOC_GETTEMP: {
/*
* Read the status byte from pcf8591 chip. The value will
* be already converted to Celcius by translate_cputemp.
*/
}
break;
}
case ENVC_IOC_GETMODE: {
}
break;
}
case ENVC_IOC_SETMODE: {
break;
}
if (curr_mode == ENVCTRL_DIAG_MODE ||
curr_mode == ENVCTRL_NORMAL_MODE) {
}
break;
}
/* Testing, may be removed */
case I2CDEV_TRAN:
break;
}
!= I2C_SUCCESS) {
break;
}
break;
}
break;
/*
* TESTING TRANSLATION from "adc" "table" property
* translate thermistor index into temp Celcius
*/
case I2CDEV_GETTEMP: {
break;
}
break;
}
/*
* Throw away the first byte according to PCF8591 datasheet,
* so read two bytes
*/
!= I2C_SUCCESS) {
break;
}
#ifdef DEBUG
if (pcf8591_debug & 0x0010)
"pcf8591_ioctl: i2c_rlen=%d; "
"i2c_rbuf[0,1]=0x%x,0x%x\n",
#endif /* DEBUG */
/*
* Throw away the first byte according to PCF8591 datasheet
*/
== 0) {
break;
}
break;
}
break;
}
case I2CDEV_GETTABLES: {
break;
}
default:
}
unitp->pcf8591_flags = 0;
return (err);
}
static int
{
int instance;
if (attach_flag & PCF8591_KSTAT_INIT) {
}
if (attach_flag & PCF8591_LOCK_INIT) {
}
/*
* Restore the lengths of the rbuf and wbuf, which was originally
* allocated so that the appropriate amount of rbuf and wbuf are
* freed.
*/
if (attach_flag & PCF8591_ALLOC_TRANSFER) {
}
if (attach_flag & PCF8591_REGISTER_CLIENT) {
}
if (attach_flag & PCF8591_MINORS_CREATED) {
}
/*
* Free the memory allocated for the properties.
*/
if (attach_flag & PCF8591_PROPS_READ) {
}
}
}
if (attach_flag & PCF8591_SOFT_STATE_ALLOC) {
}
return (DDI_SUCCESS);
}
static int
{
return (ENXIO);
}
/*
* Set the busy flag so that future transactions block
* until resume.
*/
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
static int
{
return (ENXIO);
}
unitp->pcf8591_flags = 0;
return (DDI_SUCCESS);
}
static int
{
int i, instance;
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
(void) pcf8591_do_detach(dip);
return (DDI_FAILURE);
}
/*
* Set the current operating mode to NORMAL_MODE.
*/
/*
* Create a minor node corresponding to channel 0 to 3
*/
for (i = 0; i < PCF8591_MAX_CHANS; i++) {
if (i == 0) {
} else {
}
(void) pcf8591_do_detach(dip);
return (DDI_FAILURE);
}
}
!= I2C_SUCCESS) {
(void) pcf8591_do_detach(dip);
return (DDI_FAILURE);
}
/*
* We allocate a single i2c_transfer_t structure for all
* i2c transactions.
*/
(void) pcf8591_do_detach(dip);
return (DDI_FAILURE);
}
/*
* The flags will be set to I2C_WR because for all reads from
* the 8591 we need to also write the control byte.
*/
/*
* Set the analog programming mode to default. Upper nibble
* in control byte. Four single ended inputs, output not enabled.
*/
/*
* Set the open flag for each channel to 0.
*/
for (i = 0; i < PCF8591_MAX_CHANS; i++) {
unitp->pcf8591_oflag[i] = 0;
}
/*
* Set the busy flag to 0.
*/
unitp->pcf8591_flags = 0;
(void) pcf8591_do_detach(dip);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/* ARGSUSED */
static int
{
int instance;
if (infocmd == DDI_INFO_DEVT2INSTANCE) {
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
static int
{
switch (cmd) {
case DDI_ATTACH:
return (pcf8591_do_attach(dip));
case DDI_RESUME:
return (pcf8591_do_resume(dip));
default:
return (DDI_FAILURE);
}
}
static int
{
switch (cmd) {
case DDI_DETACH:
return (pcf8591_do_detach(dip));
case DDI_SUSPEND:
return (pcf8591_do_suspend(dip));
default:
return (DDI_FAILURE);
}
}
static uint8_t
{
return (_cpu_temps[value]);
}
static int
{
return (DDI_FAILURE);
}
/*
* The kstat fields are already initialized in the attach routine..
*/
return (DDI_SUCCESS);
}
static void
{
}
static int
{
char *kstatp;
int err = 0;
int channel = 0;
int warn_temp = 0;
int shutdown_temp = 0;
return (EINTR);
}
}
if (rw == KSTAT_WRITE) {
/* check for the size of buffer */
goto bail;
}
goto bail;
}
goto bail;
}
/* write into kstat fields */
} else {
sizeof (unitp->temp_kstats));
}
bail:
unitp->pcf8591_flags = 0;
return (err);
}
static int
int size)
{
/*
* We need to read an extra byte, since as per specification
* the first byte read should be discarded.
*/
channel);
if (retval == I2C_SUCCESS) {
}
}
return (retval);
}
/*
* Reads the properties of the pcf8591 device.
*/
static int
{
char *function;
#ifdef lint
#endif
/*
* Check for the pcf8591_function property, and make sure it's
* cputemp.
*/
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
if (retval != DDI_PROP_SUCCESS) {
return (retval);
}
#ifdef DEBUG
else if (pcf8591_debug & 0x02)
"pcf8591_read_props:ddi_prop_lookup_string(%s): \
#endif /* DEBUG */
if (retval == DDI_PROP_SUCCESS) {
#ifdef DEBUG
if (pcf8591_debug & 0x02)
"pcf8591:ddi_getlongprop(%s) returns %d,"
" i2c_bus,slave=0x%x,0x%x",
#endif /* DEBUG */
} else {
#ifdef DEBUG
"pcf8591_read_props:ddi_getlongprop(%s) returns %d,"
" default it to 0x%x:0x%X",
#endif /* DEBUG */
}
"channels-in-use", &prop_len);
"channels-in-use",
if (retval == DDI_PROP_SUCCESS) {
} else {
}
#ifdef DEBUG
if (pcf8591_debug & 0x0002)
"pcf8591_read_props:ddi_prop_lookup_byte_array(%s)"
"returns %d\n"
"\t\tlength=%d, #elements=%d",
"channels-in-use", retval,
#endif /* DEBUG */
DDI_PROP_DONTPASS, "channels-description",
if (retval != DDI_PROP_SUCCESS) {
prop_len = 0;
}
#ifdef DEBUG
if (pcf8591_debug & 0x0002) {
"pcf8591_read_props:ddi_prop_lookup_string_array(%s)"
"returns %d, length=%d",
for (i = 0; i < prop_len; ++i) {
}
}
#endif /* DEBUG */
/*
* The following code was borrowed from envctrltwo.c
* I haven't yet investigated why the copy target is index + 2
*/
if (retval != DDI_PROP_SUCCESS) {
#ifdef DEBUG
#endif /* DEBUG */
return (DDI_NOT_WELL_FORMED);
}
for (i = 0; i < prop_len; i++) {
_cpu_temps[i] = creg_prop[i];
}
}
#ifdef DEBUG
if (pcf8591_debug & 0x0002)
#endif /* DEBUG */
/*
* Read shutdown temp and warning temp properties.
*/
/*
* Fill up the warning and shutdown temp values in kstat structure.
*/
return (DDI_PROP_SUCCESS);
}