smbus.c revision a195726fa33097e56cf1c25c31feddb827e140f0
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This is the nexus driver for SMBUS devices. It mostly does not use
* the SMBUS protocol so that it fits better into the solaris i2c
* framework.
*/
#include <sys/archsystm.h>
#include <sys/platform_module.h>
/*
* static function declarations
*/
static void smbus_intr_timeout(void *arg);
i2c_transfer_t *tp);
static void smbus_release(smbus_t *);
static struct bus_ops smbus_busops = {
nullbusmap, /* bus_map */
NULL, /* bus_get_intrspec */
NULL, /* bus_add_intrspec */
NULL, /* bus_remove_intrspec */
NULL, /* bus_map_fault */
ddi_no_dma_map, /* bus_dma_map */
ddi_no_dma_allochdl, /* bus_dma_allochdl */
ddi_no_dma_freehdl, /* bus_dma_freehdl */
ddi_no_dma_bindhdl, /* bus_dma_bindhdl */
ddi_no_dma_unbindhdl, /* bus_unbindhdl */
ddi_no_dma_flush, /* bus_dma_flush */
ddi_no_dma_win, /* bus_dma_win */
ddi_no_dma_mctl, /* bus_dma_ctl */
smbus_bus_ctl, /* bus_ctl */
ddi_bus_prop_op, /* bus_prop_op */
NULL, /* bus_get_eventcookie */
NULL, /* bus_add_eventcall */
NULL, /* bus_remove_eventcall */
NULL, /* bus_post_event */
0, /* bus_intr_ctl */
0, /* bus_config */
0, /* bus_unconfig */
0, /* bus_fm_init */
0, /* bus_fm_fini */
0, /* bus_fm_access_enter */
0, /* bus_fm_access_exit */
0, /* bus_power */
i_ddi_intr_ops /* bus_intr_op */
};
struct cb_ops smbus_cb_ops = {
nodev, /* open */
nodev, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
nodev, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
0, /* streamtab */
};
0,
};
&mod_driverops, /* Type of module. This one is a driver */
"SMBUS nexus Driver %I%", /* Name of the module. */
&smbus_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
&modldrv,
};
/*
* Globals
*/
static void *smbus_state;
static int intr_timeout = INTR_TIMEOUT;
/*
* The "interrupt-priorities" property is how a driver can specify a SPARC
* PIL level to associate with each of its interrupt properties. Most
* self-identifying busses have a better mechanism for managing this, but I2C
* doesn't.
*/
};
#ifdef DEBUG
static int smbus_print_lvl = 0;
static char msg_buff[1024];
static kmutex_t msg_buf_lock;
void
{
if (flags & smbus_print_lvl) {
if (smbus_print_lvl & PRT_PROM) {
} else {
if (smbus_print_lvl & PRT_BUFFONLY) {
} else {
}
}
}
}
#endif /* DEBUG */
int
_init(void)
{
int status;
1);
if (status != 0) {
return (status);
}
} else {
#ifdef DEBUG
#endif
}
return (status);
}
int
_fini(void)
{
int status;
#ifdef DEBUG
#endif
}
return (status);
}
/*
* The loadable-module _info(9E) entry point
*/
int
{
}
static void
{
int src_enable;
src_enable |= SMBUS_SMI;
}
static void
{
int src_enable;
src_enable &= ~SMBUS_SMI;
}
static void
{
return;
}
"interrupt-priorities");
}
}
}
if (smbus->smbus_timeout != 0) {
}
}
}
static int
{
/*
* Allocate soft state structure.
*/
goto bad;
}
goto bad;
}
"interrupts") == 1) {
smbus->smbus_polling = 0;
/*
* The "interrupt-priorities" property is how a driver can
* specify a SPARC PIL level to associate with each of its
* interrupt properties. Most self-identifying busses have
* a better mechanism for managing this, but I2C doesn't.
*/
"interrupt-priorities") != 1) {
DDI_PROP_CANSLEEP, "interrupt-priorities",
sizeof (smbus_pil));
}
/*
* Clear status to clear any possible interrupt
*/
DDI_SUCCESS) {
goto bad;
}
smbus->smbus_name);
goto bad;
}
} else {
/* Clear status */
}
/*
* initialize a cv and mutex
*/
(void *)smbus->smbus_icookie);
/*
* Register with the i2c framework
*/
return (DDI_SUCCESS);
bad:
return (DDI_FAILURE);
}
static int
{
switch (cmd) {
case DDI_ATTACH:
return (smbus_doattach(dip));
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
{
switch (cmd) {
case DDI_DETACH:
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
{
switch (op) {
case DDI_CTLOPS_INITCHILD:
case DDI_CTLOPS_UNINITCHILD:
return (DDI_SUCCESS);
return (DDI_SUCCESS);
case DDI_CTLOPS_DMAPMAPC:
case DDI_CTLOPS_POKE:
case DDI_CTLOPS_PEEK:
case DDI_CTLOPS_IOMIN:
case DDI_CTLOPS_REPORTINT:
case DDI_CTLOPS_SIDDEV:
case DDI_CTLOPS_SLAVEONLY:
case DDI_CTLOPS_AFFINITY:
case DDI_CTLOPS_PTOB:
case DDI_CTLOPS_BTOP:
case DDI_CTLOPS_BTOPR:
case DDI_CTLOPS_DVMAPAGESIZE:
return (DDI_FAILURE);
default:
}
}
static int
{
int len;
int err;
char name[30];
ddi_node_name(cdip)));
DDI_PROP_CANSLEEP, "#address-cells",
return (DDI_FAILURE);
}
if (err != DDI_PROP_SUCCESS) {
return (DDI_FAILURE);
}
/*
* The reg property contains an unused first element (which is
* the mux addr on xcal), and the second element is the i2c bus
* address of the device.
*/
ddi_node_name(cdip)));
return (DDI_SUCCESS);
}
static void
{
}
static void
{
}
/*
* smbus_setup_regs() is called to map in the registers
* specific to the smbus.
*/
static int
{
int ret;
if (ret == DDI_FAILURE) {
} else if (ret == DDI_REGS_ACC_CONFLICT) {
"%s unable to map regs because of conflict",
smbus->smbus_name);
ret = DDI_FAILURE;
}
if (ret == DDI_FAILURE) {
return (ret);
}
if (ret == DDI_FAILURE) {
smbus->smbus_name);
} else if (ret == DDI_REGS_ACC_CONFLICT) {
"%s unable to map config regs because of conflict",
smbus->smbus_name);
ret = DDI_FAILURE;
}
return (ret);
}
/*
* smbus_free_regs() frees any registers previously allocated.
*/
static void
{
}
}
}
/*
* smbus_dip_to_addr() takes a dip and returns an I2C address.
*/
static int
{
return (ppvt->smbus_ppvt_addr);
}
/*
* smbus_suspend() is called before the system suspends. Existing
* transfer in progress or waiting will complete, but new transfers are
* effectively blocked by "acquiring" the bus.
*/
static void
{
int instance;
}
/*
* smbus_resume() is called when the system resumes from CPR. It releases
* the hold that was placed on the i2c bus, which allows any real
* transfers to continue.
*/
static void
{
int instance;
}
/*
* smbus_acquire() is called by a thread wishing to "own" the SMbus.
* It should not be held across multiple transfers.
*/
static int
{
while (smbus->smbus_busy) {
}
/*
* On systems where OBP shares a smbus controller with the
* OS, plat_shared_i2c_enter will serialize access to the
* smbus controller. Do not grab this lock during CPR
* suspend as the CPR thread also acquires this muxex
* through through prom_setprop which causes recursive
* mutex enter.
*
* dip == NULL during CPR.
*/
}
return (SMBUS_SUCCESS);
}
/*
* smbus_release() is called to release a hold made by smbus_acquire().
*/
static void
{
smbus->smbus_busy = 0;
}
}
static void
{
/*
* if FLUSH flag is passed, read a config regs to make sure
* data written is flushed.
*/
if (flags & SMBUS_FLUSH) {
}
}
static uint8_t
{
return (data);
}
/*
* The southbridge smbus device appears to have a feature where
* reads from the status register return 0 for a few microseconds
* after clearing the status.
*
* "status_wait_idle" allows for this by retrying until
* it gets the right answer or times out. The loop count
* and the delay are empirical. The routine uses up
* 400 us if it fails.
*
* The fact that this routine waits for 10 us before the
* first check is deliberate.
*/
static int
{
int retries = 40;
int status;
do {
drv_usecwait(10);
return (status);
}
/*
* smbus_transfer is the function that is registered with
* I2C services to be called for each i2c transaction.
*/
int
{
return (I2C_FAILURE);
}
smbus->smbus_retries = 0;
smbus->smbus_bytes_to_read = 0;
/*
* First clear the status bits, then read them back to determine
* the current state.
*/
/*
* Try to issue bus reset
* First reset the state machine.
*/
"%s smbus not idle. Unable to reset %x",
return (I2C_FAILURE);
} else {
smbus->smbus_name);
}
}
}
if (smbus->smbus_polling) {
smbus->smbus_poll_complete = 0;
smbus->smbus_poll_retries = 0;
do {
} while (!smbus->smbus_poll_complete);
} else {
/*
* Start a timeout as there is a bug in southbridge
* smbus where sometimes a transaction never starts,
* and needs to be reinitiated.
*/
"starting timeout in smbus_transfer %p",
smbus->smbus_timeout));
ctime = ddi_get_lbolt();
}
}
return (tp->i2c_result);
}
/*
* This is called by smbus_intr_cmn() to figure out whether to call
* smbus_wr or smbus_rd depending on the command and current state.
*/
static int
{
int ret;
"%s smbus_cur_tran is NULL. Transaction failed",
smbus->smbus_name);
return (SMBUS_FAILURE);
}
case I2C_WR:
break;
case I2C_RD:
break;
case I2C_WR_RD:
/*
* We could do a bit more decoding here,
* to allow the transactions that would
* work as a single smbus command to
* be done as such. It's not really
* worth the trouble.
*/
if (tp->i2c_w_resid > 0) {
} else {
}
break;
default:
break;
}
return (ret);
}
/*
*
*/
static void
smbus_intr_timeout(void *arg)
{
/*
* If timeout is already cleared, it means interrupt arrived
* while timeout fired. In this case, just return from here.
*/
if (smbus->smbus_timeout == 0) {
return;
}
}
/*
* smbus_intr() is the interrupt handler for smbus.
*/
static uint_t
{
/*
* Check to see if intr is really from smbus
*/
if ((intr_status & SMBUS_SMB_INTR_STATUS) == 0) {
return (DDI_INTR_UNCLAIMED);
}
/*
* If timeout is already cleared, it means it arrived before the intr.
* In that case, just return from here.
*/
if (smbus->smbus_timeout == 0) {
return (DDI_INTR_CLAIMED);
}
return (result);
}
/*
* smbus_intr() is the interrupt handler for smbus.
*/
static uint_t
{
char error_str[128];
int ret = SMBUS_SUCCESS;
error_str[0] = '\0';
/*
* This only happens when top half is interrupted or
* times out, then the interrupt arrives. Interrupt
* was already disabled by top half, so just exit.
*/
return (DDI_INTR_CLAIMED);
}
/*
* This wait is required before reading the status, otherwise
* a parity error can occur which causes a panic. A bug with
* southbridge SMBUS.
*/
drv_usecwait(15);
if (smbus->smbus_polling) {
/*
* If we are polling, then we expect not to
* get the right answer for a while,
* so we don't go on to that error stuff
* until we've polled the status for a
* few times. We check for errors here to save time,
* otherwise we would have to wait for the full
* poll timeout before dealing with them.
*/
return (DDI_INTR_CLAIMED);
}
/*
* else either ...
* [] the command has completed, or;
* [] There has been an error, or;
* [] we timed out waiting for something useful
* to happen, so we go on to to the error handling bit that
* follows, * which will reset the controller then restart the
* whole transaction.
*
* In all cases, clear "poll_retries" for the next command or
* retry
*/
smbus->smbus_poll_retries = 0;
}
/*
* A bug in southbridge SMBUS sometimes requires a reset. Status
* should NOT be IDLE without any other bit set. If it is, the
* transaction should be restarted.
*/
}
}
}
}
}
if (error_str[0] != '\0') {
}
/*
* Clear status to clear the interrupt.
*/
if (error_str[0] != '\0') {
/*
* XXXX There was a panic here when the
* intr timeout was greater than the timeout
* for the entire transfer.
*
* Restore the value of w_resid before the
* last transaction. r_resid doesn't need to
* be restored because it is only decremented
* after a successful read. Need to do this
* here since smbus_switch() keys off of a
* resid to know whether to call smbus_rd() or
* smbus_wr().
*/
smbus->smbus_bytes_to_read = 0;
"retrying: %s %s w_resid=%d\n", error_str,
} else {
/*
* bailing, but first will reset the bus.
*/
ret = SMBUS_FAILURE;
}
} else {
smbus->smbus_retries = 0;
}
}
if (ret != SMBUS_FAILURE) {
}
if (smbus->smbus_polling) {
}
} else {
/*
* Disable previous timeout. In case it was about to fire this
* will let it exit without doing anything.
*/
smbus->smbus_timeout = 0;
} else {
}
}
return (DDI_INTR_CLAIMED);
}
/*
* smbus_wr handles writes to the smbus. Unlike true I2C busses
* such as provided by pcf8584, smbus attaches a start and stop bit for each
* transaction, so this limits writes to the maximum number of bytes
* in a single transaction, which is 33.
*
* If more than 33 bytes are contained in the transfer, a non-zero
* residual has to be returned, and the calling driver has to restart
* another transaction to complete writing out any remaining data. The
* first byte to be written for each SMBUS transaction.
*/
static int
{
int bytes_written = 0;
uint8_t a;
uint8_t b;
return (SMBUS_COMPLETE);
}
/*
* Address must be re-written for each command and it has to
* be written before SMB_TYP.
*/
switch (tp->i2c_w_resid) {
case 1:
" %d\n", a));
break;
case 2:
" %d %d\n", a, b));
break;
default:
/*
* Write out as many bytes as possible in a single command.
* Note that BLK_DATA just creats a byte stream. ie, the
* smbus protocol is not used or interpreted by this driver.
*/
while (tp->i2c_w_resid != 0) {
/*
* Note that MAX_BLK_SEND defines how many bytes may
* be sent to the BLK_DATA register. The leading byte
* already sent to the SMB_CMD register doesn't count
* But ALL the BLK_DATA bytes count so pre-increment
* bytes_written before testing.
*/
if (++bytes_written == MAX_BLK_SEND) {
break;
}
}
break;
}
/*
* writing anything to port reg starts transfer
*/
return (SMBUS_PENDING);
}
/*
* smbus_rd handles reads to the smbus. Unlike a true I2C bus
* such as provided by pcf8584, smbus attaches a start and stop bit
* for each transaction, which limits reads to the maximum number of
* bytes in a single SMBUS transaction. (Block reads don't
* seem to work on smbus, and the southbridge documentation is poor).
*
* It doesn't appear that reads spanning multiple I2C transactions
* (ie each with a start-stop) affects the transfer when reading
* multiple bytes from devices with internal counters. The counter
* is correctly maintained.
*
* RD_WORD and RD_BYTE write out the byte in the SMB_CMD register
* before reading, so RCV_BYTE is used instead.
*
* Multi-byte reads iniatiate a SMBUS transaction for each byte to be
* be resent for each I2C transaction (as opposed to when writing data),
* the driver can continue reading data in separate SMBUS transactions
* until the requested buffer is filled.
*/
static int
{
tp->i2c_r_resid--;
smbus->smbus_bytes_to_read = 0;
if (tp->i2c_r_resid == 0) {
return (SMBUS_COMPLETE);
}
}
/*
* Address must be re-written for each command. It must
* be written before SMB_TYP.
*/
if (tp->i2c_r_resid == 0) {
smbus->smbus_bytes_to_read = 0;
return (SMBUS_COMPLETE);
}
return (SMBUS_PENDING);
}