/*
* 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.
*/
/*
* Daktari platform specific hotplug controller. This
* driver exports the same interfaces to user space
* as the generic hpc3130 driver. It adds specific
* functionality found on Daktari, such as slot button
* and platform specific LED displays. Placed in
* the daktari specific platform directory, it will
* be loaded instead of the generic module.
*/
#include <sys/hpc3130_events.h>
#include <sys/hpc3130_dak.h>
#ifdef DEBUG
static int hpc3130debug = 0;
#else
#endif /* DEBUG */
struct tuple {
};
struct connect_command {
};
{
{HPC3130_EVENT_STATUS, 0xff},
{HPC3130_NO_REGISTER, 0},
};
{
{HPC3130_EVENT_STATUS, 0xff},
{HPC3130_NO_REGISTER, 0},
};
{
{B_FALSE, HPC3130_CLKON},
{B_FALSE, HPC3130_REQ64},
};
sizeof (struct connect_command))
struct xlate_entry {
char *nexus;
int pcidev;
};
/*
* The order here is significant. Its the order
* of appearance of slots from bottom to top
* on a Sun-Fire-880
*/
{
{"/pci@8,700000", 5}, /* PCI0 */
{"/pci@8,700000", 4}, /* PCI1 */
{"/pci@8,700000", 3}, /* PCI2 */
{"/pci@8,700000", 2}, /* PCI3 */
{"/pci@9,700000", 4}, /* PCI4 */
{"/pci@9,700000", 3}, /* PCI5 */
{"/pci@9,700000", 2}, /* PCI6 */
{"/pci@9,600000", 2}, /* PCI7 */
{"/pci@9,600000", 1} /* PCI8 */
};
sizeof (struct xlate_entry))
static int hpc3130_atoi(const char *);
int hpc3130_lookup_slot(char *, int);
static int hpc3130_do_attach(dev_info_t *);
static int hpc3130_do_detach(dev_info_t *);
static int hpc3130_do_resume(void);
static int hpc3130_do_suspend();
/*
* cb ops
*/
hpc3130_open, /* open */
hpc3130_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
hpc3130_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
hpc3130_poll, /* poll */
ddi_prop_op, /* cb_prop_op */
NULL, /* streamtab */
CB_REV, /* rev */
nodev, /* int (*cb_aread)() */
nodev /* int (*cb_awrite)() */
};
/*
* dev ops
*/
void **result);
0,
NULL, /* bus_ops */
NULL, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
extern struct mod_ops mod_driverops;
&mod_driverops, /* type of module - driver */
"Hotplug controller driver",
};
0
};
int
_init(void)
{
int error;
if (!error)
(void) ddi_soft_state_init((void *)&hpc3130soft_statep,
sizeof (hpc3130_unit_t), 4);
return (error);
}
int
_fini(void)
{
int error;
if (!error)
ddi_soft_state_fini((void *)&hpc3130soft_statep);
return (error);
}
int
{
}
static int
{
int instance;
int error = 0;
return (EINVAL);
}
unitp = (hpc3130_unit_t *)
return (ENXIO);
}
if (unitp->hpc3130_oflag != 0) {
} else {
}
} else {
} else {
}
}
return (error);
}
static int
{
int instance;
unitp = (hpc3130_unit_t *)
return (ENXIO);
}
unitp->hpc3130_oflag = 0;
return (DDI_SUCCESS);
}
static int
{
"ioctl = NULL"));
return (EINVAL);
}
if (i2c_tran_pointer == NULL) {
" i2c_tran_pointer not allocated"));
return (ENOMEM);
}
if (err) {
" i2c_trasfer routine"));
return (err);
}
i2c_tran_pointer->i2c_rbuf[0]));
" ddi_copyout routine"));
}
return (err);
}
static int
{
"ioctl = NULL"));
return (EINVAL);
}
"ddi_copyin routine"));
return (EFAULT);
}
2, 0, I2C_SLEEP);
if (i2c_tran_pointer == NULL) {
"HPC3130_SET_CONTROL i2c_tran_pointer not allocated"));
return (ENOMEM);
}
return (err);
}
static int
int *rvalp)
{
int err = DDI_SUCCESS;
unitp = (hpc3130_unit_t *)
return (ENOMEM);
}
/*
* It should be the case that the port number is a valid
* index in the per instance slot table. If it is not
* then we should fail out.
*/
return (EINVAL);
}
switch (cmd) {
case HPC3130_GET_STATUS:
mode);
break;
case HPC3130_GET_CONTROL:
mode);
break;
case HPC3130_SET_CONTROL:
break;
}
mode);
break;
case HPC3130_GET_EVENT_STATUS:
break;
case HPC3130_SET_EVENT_STATUS:
break;
break;
break;
break;
break;
case HPC3130_GET_EVENT_ENABLE:
break;
case HPC3130_SET_EVENT_ENABLE:
break;
"HPC3130_SLOT_CONTROL_ENABLE"));
break;
"HPC3130_SLOT_CONTROL_DISABLE"));
break;
case I2C_GET_REG:
"ioctl = NULL"));
break;
}
"ddi_copyin routine"));
break;
}
if (i2c_tran_pointer == NULL) {
"i2c_tran_pointer not allocated"));
break;
}
if (err) {
"i2c_transfer routine"));
break;
}
"ddi_copyout routine"));
}
break;
case I2C_SET_REG:
"ioctl = NULL"));
break;
}
"ddi_copyin routine"));
break;
}
2, 0, I2C_SLEEP);
if (i2c_tran_pointer == NULL) {
"i2c_tran_pointer not allocated"));
break;
}
if (err) {
"i2c_transfer routine"));
break;
}
break;
case HPC3130_GET_EVENT: {
} else {
}
}
" ddi_copyout routine"));
}
break;
}
case HPC3130_CONF_DR: {
int dr_conf;
sizeof (int), mode) != DDI_SUCCESS) {
"ddi_copyin routine"))
break;
}
break;
}
default:
}
return (err);
}
static int
{
return (EINVAL);
}
unitp = (hpc3130_unit_t *)
} else {
*reventsp = 0;
if (!anyyet)
}
return (0);
}
/* ARGSUSED */
static int
{
int instance;
if (infocmd == DDI_INFO_DEVT2INSTANCE) {
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
static int
{
switch (cmd) {
case DDI_ATTACH:
return (hpc3130_do_attach(dip));
case DDI_RESUME:
return (hpc3130_do_resume());
default:
return (DDI_FAILURE);
}
}
static int
{
switch (cmd) {
case DDI_DETACH:
return (hpc3130_do_detach(dip));
case DDI_SUSPEND:
return (hpc3130_do_suspend());
default:
return (DDI_FAILURE);
}
}
static int
{
char *s;
char *nexus;
char *pcidev;
char *reg_offset;
int r, i, n, j;
/*
* Allocate the soft state structure for this instance.
*/
if (r != DDI_SUCCESS) {
return (DDI_FAILURE);
}
sizeof (hpc3130_pil)) != DDI_PROP_SUCCESS) {
goto failout0;
}
if (ddi_intr_hilevel(dip, 0)) {
goto failout0;
}
/*
* Get the "slot-table" property which defines the list of
* hot-pluggable slots for this controller along with the
* corresponding bus nexus node and device identification
* for each slot.
*/
switch (r) {
case DDI_PROP_SUCCESS:
break;
case DDI_PROP_NOT_FOUND:
"couldn't find slot-table property");
return (DDI_FAILURE);
case DDI_PROP_UNDEFINED:
"slot-table undefined");
return (DDI_FAILURE);
case DDI_PROP_NO_MEMORY:
"can't allocate memory for slot-table");
return (DDI_FAILURE);
}
/*
* Determine the size of the slot table from the OBP property and
* allocate the slot table arrary..
*/
for (i = 0, n = 0; i < hpc3130_p->hpc3130_slot_table_size; i++) {
if (hpc3130_p->hpc3130_slot_table_data[i] == 0) {
n++;
}
}
/*
* There should be HPC3130_TABLE_COLUMNS elements per entry
*/
if (n % HPC3130_TABLE_COLUMNS) {
goto failout1;
}
DDI_SUCCESS) {
goto failout1;
}
(void *)hpc3130_p->ic_trap_cookie);
/*
* Create enough space for each slot table entry
* based on how many entries in the property
*/
sizeof (hpc3130_slot_table_entry_t), KM_SLEEP);
/*
* Setup to talk to the i2c nexus
*/
goto failout2;
}
for (i = 0; i < hpc3130_p->hpc3130_slot_table_length; i++) {
/* Pick off pointer to nexus path */
nexus = s;
s = s + strlen(s) + 1;
/* Pick off pointer to 3130 register offset */
reg_offset = s;
s = s + strlen(s) + 1;
/* Pick off pointer to the device number */
pcidev = s;
s = s + strlen(s) + 1;
j = hpc3130_atoi(reg_offset);
if (j < 0 || j >= HPC3130_MAX_SLOT) {
"invalid register offset value");
goto failout3;
}
hpc3130_atoi(pcidev)));
} else {
}
/*
* The "callback_info" structure of the slot_table is what gets
* passed back in the callback routines. All that is needed
* at that point is the device handle and the register offset
* within it the chip it represents.
*/
}
goto failout4;
}
goto failout4;
}
/*
* Register with the "services" module
*/
for (i = 0; i < hpc3130_p->hpc3130_slot_table_length; i++) {
&hpc3130_p->hpc3130_slot_table[i];
}
}
sizeof (hpc3130_p->hpc3130_name),
for (i = 0; i < HPC3130_MAX_SLOT; i++) {
PORT_TO_MINOR(I2C_PORT(i));
"for %s", name));
ddi_remove_intr(dip, 0u,
goto failout4;
}
}
return (DDI_SUCCESS);
sizeof (hpc3130_slot_table_entry_t));
return (DDI_FAILURE);
}
static int
{
return (DDI_SUCCESS);
}
static int
{
return (DDI_SUCCESS);
}
static int
{
int i;
instance);
return (ENXIO);
for (i = 0; i < hpc3130_p->hpc3130_slot_table_length; i++) {
(void) hpc_slot_unregister(
}
sizeof (hpc3130_slot_table_entry_t));
return (DDI_SUCCESS);
}
int
{
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/*
* We're turning a LED on or off (i.e., not blinking), and
* the value actually did change.
*/
if (led == HPC3130_LED_OK2REM) {
"recording IEVENT_OK2REM slot=%d, val=%d",
} else {
"recording IEVENT_FAULT slot=%d, val=%d",
}
}
return (DDI_SUCCESS);
}
int
{
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
{
return (hpc3130_rw(handle,
}
static int
{
return (hpc3130_rw(handle,
}
static int
{
int err;
int rlen;
int wlen;
wlen = 2;
rlen = 0;
} else {
wlen = 1;
rlen = 1;
}
(void) i2c_transfer_alloc(handle,
if (i2c_tran_pointer == NULL) {
"no transfer structure 0x%x", reg));
return (DDI_FAILURE);
}
} else {
}
if (err) {
"no I2C data transfered 0x%x", reg));
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Put the hot plug controller(s) in proper mode for further
* operations.
*/
static int
struct tuple *init_sequence)
{
int slot;
instance);
tp = init_sequence;
goto out;
}
tp++;
}
/*
* CPU slots need some special initialization
* attention.
*/
!= DDI_SUCCESS) {
goto out;
}
}
}
error = DDI_SUCCESS;
out:
return (error);
}
/*
* When the TI 3130 produces an interrupt,
* this routine is called to sort it out.
*/
static uint_t
{
/*
* Read the interrupt event register - see
* which event(s) took place.
*/
&interrupt)) {
continue;
}
if (interrupt == 0)
continue;
continue;
}
if (interrupt & HPC3130_PWRGOOD) {
if (!(status & HPC3130_PWRGOOD)) {
}
}
if (interrupt & HPC3130_DETECT0) {
if (slot_type == HPC3130_SLOT_TYPE_SBD) {
(void) hpc3130_set_led(hpc3130_p,
slot,
(present ? HPC3130_ATTN_ON :
if (!present) {
/* Clear the FAULT LED on removal */
(void) hpc3130_set_led(hpc3130_p,
slot,
}
} else {
if (!(status & HPC3130_DETECT0)) {
/*
* Event on the downward
* stroke of the button.
*/
}
}
}
if (slot_type == HPC3130_SLOT_TYPE_SBD) {
if (!(status & HPC3130_PRSNT1)) {
/*
* Event only on the downward
* stroke of the button.
*/
}
} else {
if ((status & (HPC3130_PRSNT1 |
HPC3130_PRSNT2)) ==
(HPC3130_PRSNT1 | HPC3130_PRSNT2)) {
/* Turn OFF Fault LED */
(void) hpc3130_set_led(hpc3130_p,
slot,
/* Turn OFF OK-to-remove LED */
(void) hpc3130_set_led(hpc3130_p,
slot,
} else {
/* Turn ON OK-to-remove LED */
(void) hpc3130_set_led(hpc3130_p,
slot,
}
}
}
}
}
return (rc);
}
static int
{
&slot_status)) {
goto out;
}
&control_reg)) {
goto out;
}
/*
* For the CPU slots, the DETECT[0] pin on the HPC3130
* goes low when a CPU module is in the slot. Pulled
* high otherwise.
*/
if (slot_status & HPC3130_DETECT0) {
"[0x%x]Power off....[%d]",
slot_status, slot));
} else {
"[0x%x]Power LEFT on!!!....[%d]",
slot_status, slot));
}
/*
* Set the control register accordingly
*/
goto out;
}
out:
return (result);
}
static int
{
/*
* Get HPC3130_DEBOUNCE_COUNT consecutive equal
* readings from the status register
*/
do {
return (DDI_FAILURE);
}
count = 0;
} else {
count += 1;
}
limit += 1;
} while (count < HPC3130_DEBOUNCE_COUNT &&
if (limit == HPC3130_DEBOUNCE_LIMIT) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
{
int i;
/*
* Callback parameter has specific device handle and offset
* information in it.
*/
} else {
}
goto out;
}
/* Return (do nothing) if power already applied */
return (HPC_SUCCESS);
}
&status)) {
goto out;
}
/* Read the slot control register to get current value */
&control)) {
goto out;
}
if (slot_type == HPC3130_SLOT_TYPE_SBD) {
control) != DDI_SUCCESS) {
goto out;
}
} else {
/*
* PCI needs special sequencing of the control signals.
*/
&config)) {
goto out;
}
/* Assert RST to comply with PCI spec. */
control &= ~HPC3130_SLOTRST;
control) != DDI_SUCCESS) {
goto out;
}
/* Send the power on signal and verify the result */
control) != DDI_SUCCESS) ||
goto out;
}
/* The slot is now powered on. */
/* Extinguish the "OK-to-remove" indicator */
/*
*/
goto out;
}
if ((config & HPC3130_SYSM66STAT) &&
!(status & HPC3130_M66EN)) {
"33Mhz card in %s", phys_slot);
goto out;
}
if (!(config & HPC3130_SYSM66STAT) &&
(status & HPC3130_M66EN)) {
"back to 33Mhz in %s", phys_slot);
}
/*
* Send the connect sequence (see struct connect_sequence)
*/
for (i = 0; i < HPC3130_CONNECT_SEQ_COUNT; i++) {
} else {
}
control) != DDI_SUCCESS) {
goto out;
}
}
}
/* Flash the "fault" indicator */
out:
if (needs_to_be_powered_off == B_TRUE) {
/*
* We are in an error state where the slot is powered on, and
* it must be powered off.
*/
/* Send the power off signal and verify the result */
control) == DDI_SUCCESS) &&
/* Re-light "OK-to-remove" LED */
}
}
return (result);
}
static int
{
/*
* Callback parameter has specific device handle and offset
* information in it.
*/
} else {
}
/*
* Read the slot control register to get current value
*/
&control)) {
goto out;
}
if (slot_type == HPC3130_SLOT_TYPE_SBD) {
/*
* Write out the modified control register
*/
control) != DDI_SUCCESS) {
goto out;
}
} else {
control &= ~HPC3130_SLOTRST;
control) != DDI_SUCCESS) {
goto out;
}
control) != DDI_SUCCESS) {
goto out;
}
}
goto out;
}
/*
* Illuminate the "OK-to-remove" indicator
* if there is a card in the slot.
*/
/*
* Turn off the "fault" indicator
*/
} else {
/*
* If the slot is being powered off with
* no cards in there, its at "boot time",
* put the LEDs in a sane state
*/
if (slot_type == HPC3130_SLOT_TYPE_PCI) {
}
}
out:
return (result);
}
static int
{
/* This function is called while holding the hpc3130 mutex. */
/*
* For slot_target_state and slot_actual_state:
* B_TRUE == the slot is powered on
* B_FALSE == the slot is powered off
*/
while ((slot_actual_state != slot_target_state) &&
if (timeleft == -1) {
if (tries++ < HPC3130_POWER_TRIES) {
/*
* The interrupt was missed - explicitly
* check the status.
*/
if (hpc3130_read(handle,
continue;
}
if (status & HPC3130_PWRGOOD) {
} else {
}
} else {
/* Too many tries. We failed. */
}
}
}
if (slot_target_state == B_TRUE) {
"Could not power on slot %s", phys_slot);
} else {
"Could not power off slot %s", phys_slot);
}
}
return (result);
}
static int
{
return (HPC_ERR_NOTSUPPORTED);
}
static int
{
return (HPC_ERR_NOTSUPPORTED);
}
static int
{
/*
* Callback parameter has specific device handle and offset
* information in it.
*/
switch (request) {
case HPC_CTRL_GET_LED_STATE: {
int led;
"Only FAULT and ATTN leds allowed"));
return (HPC_ERR_INVALID);
}
else
DDI_SUCCESS) {
return (HPC_ERR_FAILED);
}
/* Make sure that no one broke the conversion macros */
}
break;
case HPC_CTRL_SET_LED_STATE: {
int led;
/*
* The HPC3130 support modifications to the Fault and
* Ok-to-remove LEDs.
*/
"Only FAULT and ATTN leds allowed"));
return (HPC_ERR_INVALID);
}
else
if (state >= sizeof (hpc3130_from_hpc_led_map) ||
(state != HPC3130_TO_HPC_LED(
HPC3130_FROM_HPC_LED(state)))) {
"Improper LED value: %d %d", state,
return (HPC_ERR_INVALID);
}
}
break;
case HPC_CTRL_GET_SLOT_STATE: {
*(ap_rstate_t *)arg =
} else {
*(ap_rstate_t *)arg =
}
} else {
*(ap_rstate_t *)arg =
}
}
break;
case HPC_CTRL_GET_BOARD_TYPE: {
*(hpc_board_type_t *)arg =
}
break;
break;
break;
case HPC_CTRL_DEV_CONFIGURED:
break;
} else {
}
break;
case HPC_CTRL_DISABLE_SLOT: {
}
break;
case HPC_CTRL_ENABLE_SLOT: {
}
break;
default:
return (HPC_ERR_FAILED);
}
return (HPC_SUCCESS);
}
int
{
int i = 0;
i < HPC3130_LOOKUP_SLOTS)
i++;
ASSERT(i != HPC3130_LOOKUP_SLOTS);
return (i);
}
/*
* A routine to convert a number (represented as a string) to
* the integer value it represents.
*/
static int
{
}
static int
hpc3130_atoi(const char *p)
{
int n;
int c, neg = 0;
if (!isdigit(c = *p)) {
while (isspace(c))
c = *++p;
switch (c) {
case '-':
neg++;
/* FALLTHROUGH */
case '+':
c = *++p;
}
if (!isdigit(c))
return (0);
}
for (n = '0' - c; isdigit(c = *++p); ) {
n *= 10; /* two steps to avoid unnecessary overflow */
n += '0' - c; /* accum neg to avoid surprises at MAX */
}
return (neg ? n : -n);
}