/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* i2bsc.c is the nexus driver i2c traffic against devices hidden behind the
* Blade Support Chip (BSC). It supports both interrupt and polled
* mode operation, but defaults to interrupt.
*/
#include <sys/platform_module.h>
#include <sys/lom_ebuscodes.h>
/*
* static function declarations
*/
i2c_transfer_t *tp);
static void i2bsc_release(i2bsc_t *);
static int i2bsc_setup_regs(i2bsc_t *);
static void i2bsc_start_session(i2bsc_t *);
static void i2bsc_fail_session(i2bsc_t *);
static int i2bsc_end_session(i2bsc_t *);
static void i2bsc_free_regs(i2bsc_t *);
static void i2bsc_trace(i2bsc_t *, char, const char *,
const char *, ...);
static int i2bsc_notify_max_transfer_size(i2bsc_t *);
static int i2bsc_discover_capability(i2bsc_t *);
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 */
i2bsc_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 */
};
i2bsc_open, /* open */
i2bsc_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
i2bsc_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
0, /* streamtab */
};
0,
NULL,
ddi_quiesce_not_supported, /* devo_quiesce */
};
#ifdef DEBUG
#else
#endif
&mod_driverops, /* Type of module. This one is a driver */
I2BSC_VERSION_STRING, /* Name of the module. */
&i2bsc_ops, /* driver ops */
};
&modldrv,
};
/*
* i2bsc soft state
*/
static void *i2bsc_state;
};
int
_init(void)
{
int status;
if (status != 0) {
return (status);
}
}
return (status);
}
int
_fini(void)
{
int status;
}
return (status);
}
/*
* The loadable-module _info(9E) entry point
*/
int
{
}
static void
{
}
}
}
}
}
static int
{
/*
* Allocate soft state structure.
*/
return (DDI_FAILURE);
}
DDI_PROP_DONTPASS, "debug", 0);
goto bad;
}
(void *) 0);
DDI_NT_NEXUS, 0) == DDI_FAILURE) {
i2c->i2bsc_name);
goto bad;
}
/*
* Now actually start talking to the microcontroller. The first
* thing to check is whether the firmware is broken.
*/
if (i2bsc_is_firmware_broken(i2c)) {
" shutting down my i2c services");
goto bad;
}
/*
* Now see if the BSC chip supports the i2c service we rely upon.
*/
(void) i2bsc_discover_capability(i2c);
return (DDI_SUCCESS);
bad:
return (DDI_FAILURE);
}
static int
{
switch (cmd) {
case DDI_ATTACH:
return (i2bsc_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);
}
}
/*ARGSUSED*/
static int
{
int instance;
/*
* Make sure the open is for the right file type
*/
return (EINVAL);
return (ENXIO);
/*
* Enforce exclusive access
*/
if (i2c->i2bsc_open) {
return (EBUSY);
} else
return (0);
}
/*ARGSUSED*/
static int
{
int instance;
/*
* Make sure the close is for the right file type
*/
return (EINVAL);
return (ENXIO);
i2c->i2bsc_open = 0;
return (0);
}
/*ARGSUSED*/
static int
int *rvalp)
{
int rv;
return (ENXIO);
/*
* read devctl ioctl data
*/
return (EFAULT);
}
switch (cmd) {
case DEVCTL_BUS_DEV_CREATE:
break;
case DEVCTL_DEVICE_REMOVE:
break;
}
/*
* lookup and hold child device
*/
break;
}
NDI_SUCCESS) {
}
break;
default:
}
return (rv);
}
static int
{
switch (op) {
case DDI_CTLOPS_INITCHILD:
case DDI_CTLOPS_UNINITCHILD:
case DDI_CTLOPS_REPORTDEV:
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:
}
}
/*
* i2bsc_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;
}
/*
* i2bsc_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;
}
/*
* i2bsc_acquire() is called by a thread wishing to "own" the I2C bus.
* It should not be held across multiple transfers.
*/
static void
{
while (i2c->i2bsc_busy) {
}
}
/*
* i2bsc_release() is called to release a hold made by i2bsc_acquire().
*/
static void
{
i2c->i2bsc_busy = 0;
}
static int
{
int len;
int err;
len = sizeof (address_cells);
DDI_PROP_CANSLEEP, "#address-cells",
return (DDI_FAILURE);
}
if (err != DDI_PROP_SUCCESS)
return (DDI_FAILURE);
if (address_cells == 1) {
" regs[0] = %d", regs[0]);
} else if (address_cells == 2) {
} else {
return (DDI_FAILURE);
}
/*
* Attach the parent's private data structure to the child's devinfo
* node, and store the child's address on the nexus in the child's
* devinfo node.
*/
return (DDI_SUCCESS);
}
static int
{
return (DDI_SUCCESS);
}
/*
* i2bsc_setup_regs() is called to map in registers specific to
* the i2bsc.
*/
static int
{
int nregs;
return (DDI_FAILURE);
}
if (nregs < 1) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* i2bsc_free_regs() frees any registers previously
* allocated.
*/
static void
{
}
}
/*ARGSUSED*/
static int
{
if (rdip == (dev_info_t *)0)
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
/*
* I/O Functions
*
* i2bsc_{put,get}8_once are wrapper functions to ddi_{get,put}8.
* i2bsc_{put,get}8 are equivalent functions but with retry code.
* i2bsc_bscbus_state determines underlying bus error status.
* i2bsc_clear_acc_fault clears the underlying bus error status.
*
* I/O Flags
*
* bscbus_fault - Error register in underlying bus for last IO operation.
* session_failure - Set by any failed IO command. This is a sticky flag
* reset explicitly using i2bsc_start_session
*
* Session Management
*
* i2bsc_{start,end}_session need to be used to detect an error across multiple
*/
{
}
{
LOMBUS_FAULT_REG), 0);
}
static void
{
i2c->bscbus_session_failure = 0;
}
static void
{
}
static int
{
/*
* The ONLY way to get the session status is to end the session.
* If clients of the session interface ever wanted the status mid-way
* then they are really working with multiple contigious sessions.
*/
}
static boolean_t
{
int i;
for (i = 0; i < niterations; i++) {
continue;
} else {
/*
* Firmware communication succeeded.
*/
"firmware communications okay");
return (B_FALSE);
}
}
/*
* Firmware is not communicative. Some possible causes :
* Broken hardware
* BSC held in reset
* Corrupt BSC image
* OBP incompatiblity preventing drivers loading properly
*/
return (B_TRUE);
}
static void
{
/*
* If a session failure has already occurred, reduce the level of
* retries to a minimum. This is a optimization of the failure
* recovery strategy.
*/
if (i2c->bscbus_session_failure)
retryable = 1;
while (retryable--) {
} else
break;
}
}
static uint8_t
{
/*
* If a session failure has already occurred, reduce the level of
* retries to a minimum. This is a optimization of the failure
* recovery strategy.
*/
if (i2c->bscbus_session_failure)
retryable = 1;
while (retryable--) {
} else
break;
}
return (value);
}
static void
{
}
static uint8_t
{
return (value);
}
static int
{
/*
* If the underlying hardware does not support the i2c service and
* we are not running in fake_mode, then we cannot set the
* MAX_TRANSFER_SZ.
*/
if (i2c->i2c_proxy_support == 0)
return (DDI_FAILURE);
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
/*
* Discover if the microcontroller implements the I2C Proxy Service this
* driver requires. If it does not, i2c transactions will abort with
* I2C_FAILURE, unless fake_mode is being used.
*/
static int
{
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
static int
{
int wr_rd;
/* Get a lock on the i2c devices owned by the microcontroller */
/*
* i2c client driver must timeout retry, NOT this nexus
*/
"Couldn't get transaction lock");
return (tp->i2c_result);
}
/*
* The Solaris architecture for I2C uses 10-bit I2C addresses where
* the address by bit-shifting.
*/
/*
* We have only one register used for data input and output. When
* a WR_RD is issued, this means we want to do a Random-Access-Read.
* First a series of bytes are written which define the address to
* read from. In hardware this sets an address pointer. Then a series
* bytes are to be written before reads will be issued.
*/
else
wr_rd = 0;
return (I2C_SUCCESS);
}
/*
* Function i2bsc_upload
*
* Description This function runs the i2c transfer protocol down to the
* microcontroller. Its goal is to be as reliable as possible.
* This is achieved by making all the state-less aspects
* re-tryable. For stateful aspects, we take care to ensure the
* counters are decremented only when data transfer has been
* successful.
*/
static int
{
int residual;
/*
* Total amount of data outstanding
*/
/*
* Anything in this session *could* be re-tried without side-effects.
* Therefore, error exit codes are I2C_INCOMPLETE rather than
* I2C_FAILURE.
*/
return (I2C_INCOMPLETE);
return (I2C_INCOMPLETE);
/* The writes done here are not retryable */
tp->i2c_w_resid--;
quota--;
residual--;
} else {
}
}
/* The reads done here are not retryable */
tp->i2c_r_resid--;
quota--;
residual--;
} else {
}
}
/*
* A possible future enhancement would be to allow early breakout of the
* loops seen above. In such circumstances, "residual" would be non-
* zero. This may be useful if we want to support the interruption of
* transfer part way through an i2c_transfer_t.
*/
switch (res) {
case EBUS_I2C_SUCCESS:
break;
case EBUS_I2C_FAILURE:
/*
* This is rare but possible. A retry may still fix this
* so lets allow that by returning I2C_INCOMPLETE.
* "hifTxRing still contains 1 bytes" is reported by the
* microcontroller when this return value is seen.
*/
" but returning I2C_INCOMPLETE for possible re-try");
break;
case EBUS_I2C_INCOMPLETE:
break;
default:
}
return (tp->i2c_result);
}
/*
* Function i2bsc_safe_upload
*
* Description This function is called "safe"-upload because it attempts to
* do transaction re-tries for cases where state is not spoiled
* by a transaction-level retry.
*/
static int
{
int result;
/*
* The only re-tryable transaction type is I2C_WR_RD. If we don't
* have this we can only use session-based recovery offered by
* i2bsc_upload.
*/
while (retryable--) {
if (result == I2C_INCOMPLETE) {
/* Have another go */
continue;
} else {
"Exiting while loop on result %d", result);
return (result);
}
}
return (result);
}
/*
* Function i2bsc_transfer
*
* Description This is the entry-point that clients use via the Solaris i2c
* framework. It kicks off the servicing of i2c transfer requests.
*/
int
{
if (i2c->i2c_proxy_support)
else
return (tp->i2c_result);
}
/*
* General utility routines ...
*/
#ifdef DEBUG
static void
const char *fmt, ...)
{
char *p;
p = buf;
p += strlen(p);
buf);
}
}
#else /* DEBUG */
static void
const char *fmt, ...)
{
}
#endif /* DEBUG */