/*
* 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
* or http://www.opensolaris.org/os/licensing.
* 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.
*
* The "lombus" driver provides access to the LOMlite2 virtual registers,
* so that its clients (children) need not be concerned with the details
* of the access mechanism, which in this case is implemented via a
* packet-based protocol over a serial link connected to one of the serial
* ports of the SuperIO (SIO) chip.
*
* On the other hand, this driver doesn't generally know what the virtual
* registers signify - only the clients need this information.
*/
/*
* Header files
*/
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/intr.h>
#include <sys/kmem.h>
#include <sys/membar.h>
#include <sys/modctl.h>
#include <sys/note.h>
#include <sys/open.h>
#include <sys/poll.h>
#include <sys/spl.h>
#include <sys/stat.h>
#include <sys/strlog.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/lombus.h>
#if defined(NDI_ACC_HDL_V2)
/*
* Compiling for Solaris 9+ with access handle enhancements
*/
#define HANDLE_TYPE ndi_acc_handle_t
#define HANDLE_ADDR(hdlp) (hdlp->ah_addr)
#define HANDLE_FAULT(hdlp) (hdlp->ah_fault)
#define HANDLE_MAPLEN(hdlp) (hdlp->ah_len)
#define HANDLE_PRIVATE(hdlp) (hdlp->ah_bus_private)
#else
/*
* Compatibility definitions for backport to Solaris 8
*/
#define HANDLE_TYPE ddi_acc_impl_t
#define HANDLE_ADDR(hdlp) (hdlp->ahi_common.ah_addr)
#define HANDLE_FAULT(hdlp) (hdlp->ahi_fault)
#define HANDLE_MAPLEN(hdlp) (hdlp->ahi_common.ah_len)
#define HANDLE_PRIVATE(hdlp) (hdlp->ahi_common.ah_bus_private)
#define ddi_driver_major(dip) ddi_name_to_major(ddi_binding_name(dip))
#endif /* NDI_ACC_HDL_V2 */
/*
* Local definitions
*/
#define MYNAME "lombus"
#define NOMAJOR (~(major_t)0)
#define DUMMY_VALUE (~(int8_t)0)
#define LOMBUS_INST_TO_MINOR(i) (i)
#define LOMBUS_MINOR_TO_INST(m) (m)
#define LOMBUS_DUMMY_ADDRESS ((caddr_t)0x0CADD1ED)
#define ADDR_TO_OFFSET(a, hdlp) ((caddr_t)(a) - HANDLE_ADDR(hdlp))
#define ADDR_TO_VREG(a) ((caddr_t)(a) - LOMBUS_DUMMY_ADDRESS)
#define VREG_TO_ADDR(v) (LOMBUS_DUMMY_ADDRESS + (v))
/*
* The following definitions are taken from the datasheet
* for the National Semiconductor PC87317 (SuperIO) chip.
*
* This chip implements UART functionality as logical device 6.
* It provides all sorts of wierd modes and extensions, but we
* have chosen to use only the 16550-compatible features
* ("non-extended mode").
*
* Hardware: serial chip register numbers
*/
#define SIO_RXD 0 /* read */
#define SIO_TXD 0 /* write */
#define SIO_IER 1
#define SIO_EIR 2 /* read */
#define SIO_FCR 2 /* write */
#define SIO_LCR 3
#define SIO_BSR 3 /* wierd */
#define SIO_MCR 4
#define SIO_LSR 5
#define SIO_MSR 6
#define SIO_SCR 7
#define SIO_LBGDL 0 /* bank 1 */
#define SIO_LBGDH 1 /* bank 1 */
/*
* Hardware: serial chip register bits
*/
#define SIO_IER_RXHDL_IE 0x01
#define SIO_IER_STD 0x00
#define SIO_EIR_IPF 0x01
#define SIO_EIR_IPR0 0x02
#define SIO_EIR_IPR1 0x04
#define SIO_EIR_RXFT 0x08
#define SIO_EIR_FEN0 0x40
#define SIO_EIR_FEN1 0x80
#define SIO_FCR_FIFO_EN 0x01
#define SIO_FCR_RXSR 0x02
#define SIO_FCR_TXSR 0x04
#define SIO_FCR_RXFTH0 0x40
#define SIO_FCR_RXFTH1 0x80
#define SIO_FCR_STD (SIO_FCR_RXFTH0|SIO_FCR_FIFO_EN)
#define SIO_LCR_WLS0 0x01
#define SIO_LCR_WLS1 0x02
#define SIO_LCR_STB 0x04
#define SIO_LCR_PEN 0x08
#define SIO_LCR_EPS 0x10
#define SIO_LCR_STKP 0x20
#define SIO_LCR_SBRK 0x40
#define SIO_LCR_BKSE 0x80
#define SIO_LCR_8BIT (SIO_LCR_WLS0|SIO_LCR_WLS1)
#define SIO_LCR_EPAR (SIO_LCR_PEN|SIO_LCR_EPS)
#define SIO_LCR_STD (SIO_LCR_8BIT|SIO_LCR_EPAR)
#define SIO_BSR_BANK0 (SIO_LCR_STD)
#define SIO_BSR_BANK1 (SIO_LCR_BKSE|SIO_LCR_STD)
#define SIO_MCR_DTR 0x01
#define SIO_MCR_RTS 0x02
#define SIO_MCR_ISEN 0x08
#define SIO_MCR_STD (SIO_MCR_ISEN)
#define SIO_LSR_RXDA 0x01
#define SIO_LSR_OE 0x02
#define SIO_LSR_PE 0x04
#define SIO_LSR_FE 0x08
#define SIO_LSR_BRKE 0x10
#define SIO_LSR_TXRDY 0x20
#define SIO_LSR_TXEMP 0x40
#define SIO_LSR_ER_INF 0x80
#define SIO_MSR_DCTS 0x01
#define SIO_MSR_DDSR 0x02
#define SIO_MSR_TERI 0x04
#define SIO_MSR_DDCD 0x08
#define SIO_MSR_CTS 0x10
#define SIO_MSR_DSR 0x20
#define SIO_MSR_RI 0x40
#define SIO_MSR_DCD 0x80
/*
* Min/max/default baud rates, and a macro to convert from a baud
* rate to the number (divisor) to put in the baud rate registers
*/
#define SIO_BAUD_MIN 50
#define SIO_BAUD_MAX 115200
#define SIO_BAUD_DEFAULT 38400
#define SIO_BAUD_TO_DIVISOR(b) (115200 / (b))
/*
* Packet format ...
*/
#define LOMBUS_MASK 0xc0 /* Byte-type bits */
#define LOMBUS_PARAM 0x00 /* Parameter byte: 0b0xxxxxxx */
#define LOMBUS_LAST 0x80 /* Last byte of packet */
#define LOMBUS_CMD 0x80 /* Command byte: 0b10###XWV */
#define LOMBUS_STATUS 0xc0 /* Status byte: 0b11###AEV */
#define LOMBUS_SEQ 0x38 /* Sequence number bits */
#define LOMBUS_SEQ_LSB 0x08 /* Sequence number LSB */
#define LOMBUS_CMD_XADDR 0x04 /* Extended (2-byte) addressing */
#define LOMBUS_CMD_WRITE 0x02 /* Write command */
#define LOMBUS_CMD_WMSB 0x01 /* Set MSB on Write */
#define LOMBUS_CMD_READ 0x01 /* Read command */
#define LOMBUS_CMD_NOP 0x00 /* NOP command */
#define LOMBUS_STATUS_ASYNC 0x04 /* Asynchronous event pending */
#define LOMBUS_STATUS_ERR 0x02 /* Error in command processing */
#define LOMBUS_STATUS_MSB 0x01 /* MSB of Value read */
#define LOMBUS_VREG_LO(x) ((x) & ((1 << 7) - 1))
#define LOMBUS_VREG_HI(x) ((x) >> 7)
#define LOMBUS_BUFSIZE 8
/*
* Time periods, in nanoseconds
*
* Note that LOMBUS_ONE_SEC and some other time
* periods are defined in <sys/lombus.h>
*/
#define LOMBUS_CMD_POLL (LOMBUS_ONE_SEC/20)
#define LOMBUS_CTS_POLL (LOMBUS_ONE_SEC/20)
#define LOMBUS_CTS_TIMEOUT (LOMBUS_ONE_SEC*2)
/*
* Local datatypes
*/
enum lombus_cmdstate {
LOMBUS_CMDSTATE_IDLE,
LOMBUS_CMDSTATE_BUSY,
LOMBUS_CMDSTATE_WAITING,
LOMBUS_CMDSTATE_READY,
LOMBUS_CMDSTATE_ERROR
};
/*
* This driver's soft-state structure
*/
struct lombus_state {
/*
* Configuration data, set during attach
*/
dev_info_t *dip;
major_t majornum;
int instance;
ddi_acc_handle_t sio_handle;
uint8_t *sio_regs;
ddi_softintr_t softid;
ddi_periodic_t cycid; /* periodical callback */
/*
* Parameters derived from .conf properties
*/
boolean_t allow_echo;
int baud;
uint32_t debug;
boolean_t fake_cts;
/*
* Hardware mutex (initialised using <hw_iblk>),
* used to prevent retriggering the softint while
* it's still fetching data out of the chip FIFO.
*/
kmutex_t hw_mutex[1];
ddi_iblock_cookie_t hw_iblk;
/*
* Data protected by the hardware mutex: the watchdog-patting
* protocol data (since the dog can be patted from a high-level
* cyclic), and the interrupt-enabled flag.
*/
hrtime_t hw_last_pat;
boolean_t hw_int_enabled;
/*
* Flag to indicate that we've incurred a hardware fault on
* accesses to the SIO; once this is set, we fake all further
* accesses in order not to provoke additional bus errors.
*/
boolean_t sio_fault;
/*
* Serial protocol state data, protected by lo_mutex
* (which is initialised using <lo_iblk>)
*/
kmutex_t lo_mutex[1];
ddi_iblock_cookie_t lo_iblk;
kcondvar_t lo_cv[1];
volatile enum lombus_cmdstate cmdstate;
clock_t deadline;
uint8_t cmdbuf[LOMBUS_BUFSIZE];
uint8_t reply[LOMBUS_BUFSIZE];
uint8_t async;
uint8_t index;
uint8_t result;
uint8_t sequence;
uint32_t error;
};
/*
* The auxiliary structure attached to each child
* (the child's parent-private-data points to this).
*/
struct lombus_child_info {
lombus_regspec_t *rsp;
int nregs;
};
/*
* Local data
*/
static void *lombus_statep;
static major_t lombus_major = NOMAJOR;
static ddi_device_acc_attr_t lombus_dev_acc_attr[1] =
{
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
/*
* General utility routines ...
*/
static void
lombus_trace(struct lombus_state *ssp, char code, const char *caller,
const char *fmt, ...)
{
char buf[256];
char *p;
va_list va;
if (ssp->debug & (1 << (code-'@'))) {
p = buf;
(void) snprintf(p, sizeof (buf) - (p - buf),
"%s/%s: ", MYNAME, caller);
p += strlen(p);
va_start(va, fmt);
(void) vsnprintf(p, sizeof (buf) - (p - buf), fmt, va);
va_end(va);
buf[sizeof (buf) - 1] = '\0';
(void) strlog(ssp->majornum, ssp->instance, code, SL_TRACE,
buf);
}
}
static struct lombus_state *
lombus_getstate(dev_info_t *dip, int instance, const char *caller)
{
struct lombus_state *ssp = NULL;
dev_info_t *sdip = NULL;
major_t dmaj = NOMAJOR;
if (dip != NULL) {
/*
* Use the instance number from the <dip>; also,
* check that it really corresponds to this driver
*/
instance = ddi_get_instance(dip);
dmaj = ddi_driver_major(dip);
if (lombus_major == NOMAJOR && dmaj != NOMAJOR)
lombus_major = dmaj;
else if (dmaj != lombus_major) {
cmn_err(CE_WARN,
"%s: major number mismatch (%d vs. %d) in %s(),"
"probably due to child misconfiguration",
MYNAME, lombus_major, dmaj, caller);
instance = -1;
}
}
if (instance >= 0)
ssp = ddi_get_soft_state(lombus_statep, instance);
if (ssp != NULL) {
sdip = ssp->dip;
if (dip == NULL && sdip == NULL)
ssp = NULL;
else if (dip != NULL && sdip != NULL && sdip != dip) {
cmn_err(CE_WARN,
"%s: devinfo mismatch (%p vs. %p) in %s(), "
"probably due to child misconfiguration",
MYNAME, (void *)dip, (void *)sdip, caller);
ssp = NULL;
}
}
return (ssp);
}
/*
* Lowest-level serial I/O chip register read/write
*/
static void
sio_put_reg(struct lombus_state *ssp, uint_t reg, uint8_t val)
{
lombus_trace(ssp, 'P', "sio_put_reg", "REG[%d] <- $%02x", reg, val);
if (ssp->sio_handle != NULL && !ssp->sio_fault) {
/*
* The chip is mapped as "I/O" (e.g. with the side-effect
* bit on SPARC), therefore accesses are required to be
* in-order, with no value cacheing. However, there can
* still be write-behind buffering, so it is not guaranteed
* that a write actually reaches the chip in a given time.
*
* To force the access right through to the chip, we follow
* the write with another write (to the SCRATCH register)
* and a read (of the value just written to the SCRATCH
* register). The SCRATCH register is specifically provided
* for temporary data and has no effect on the SIO's own
* operation, making it ideal as a synchronising mechanism.
*
* If we didn't do this, it would be possible that the new
* value wouldn't reach the chip (and have the *intended*
* side-effects, such as disabling interrupts), for such a
* long time that the processor could execute a *lot* of
* instructions - including exiting the interrupt service
* routine and re-enabling interrupts. This effect was
* observed to lead to spurious (unclaimed) interrupts in
* some circumstances.
*
* This will no longer be needed once "synchronous" access
* handles are available (see PSARC/2000/269 and 2000/531).
*/
ddi_put8(ssp->sio_handle, ssp->sio_regs + reg, val);
ddi_put8(ssp->sio_handle, ssp->sio_regs + SIO_SCR, val);
membar_sync();
(void) ddi_get8(ssp->sio_handle, ssp->sio_regs + SIO_SCR);
}
}
static uint8_t
sio_get_reg(struct lombus_state *ssp, uint_t reg)
{
uint8_t val;
if (ssp->sio_handle && !ssp->sio_fault)
val = ddi_get8(ssp->sio_handle, ssp->sio_regs + reg);
else
val = DUMMY_VALUE;
lombus_trace(ssp, 'G', "sio_get_reg", "$%02x <- REG[%d]", val, reg);
return (val);
}
static void
sio_check_fault_status(struct lombus_state *ssp)
{
ssp->sio_fault = ddi_check_acc_handle(ssp->sio_handle) != DDI_SUCCESS;
}
static boolean_t
sio_faulty(struct lombus_state *ssp)
{
if (!ssp->sio_fault)
sio_check_fault_status(ssp);
return (ssp->sio_fault);
}
/*
* Check for data ready.
*/
static boolean_t
sio_data_ready(struct lombus_state *ssp)
{
uint8_t status;
/*
* Data is available if the RXDA bit in the LSR is nonzero
* (if reading it didn't incur a fault).
*/
status = sio_get_reg(ssp, SIO_LSR);
return ((status & SIO_LSR_RXDA) != 0 && !sio_faulty(ssp));
}
/*
* Check for LOM ready
*/
static boolean_t
sio_lom_ready(struct lombus_state *ssp)
{
uint8_t status;
boolean_t rslt;
/*
* The LOM is ready if the CTS bit in the MSR is 1, meaning
* that the /CTS signal is being asserted (driven LOW) -
* unless we incurred a fault in trying to read the MSR!
*
* For debugging, we force the result to TRUE if the FAKE flag is set
*/
status = sio_get_reg(ssp, SIO_MSR);
rslt = (status & SIO_MSR_CTS) != 0 && !sio_faulty(ssp);
lombus_trace(ssp, 'R', "sio_lom_ready", "S $%02x R %d F %d",
status, rslt, ssp->fake_cts);
return (rslt || ssp->fake_cts);
}
#if 0
/*
* Check for interrupt pending
*/
static boolean_t
sio_irq_pending(struct lombus_state *ssp)
{
uint8_t status;
boolean_t rslt;
/*
* An interrupt is pending if the IPF bit in the EIR is 0,
* assuming we didn't incur a fault in trying to ready it.
*
* Note: we expect that every time we read this register
* (which is only done from the interrupt service routine),
* we will see $11001100 (RX FIFO timeout interrupt pending).
*/
status = sio_get_reg(ssp, SIO_EIR);
rslt = (status & SIO_EIR_IPF) == 0 && !sio_faulty(ssp);
lombus_trace(ssp, 'I', "sio_irq_pending", "S $%02x R %d",
status, rslt);
/*
* To investigate whether we're getting any abnormal interrupts
* this code checks that the status value is as expected, and that
* chip-level interrupts are supposed to be enabled at this time.
* This will cause a PANIC (on a driver compiled with DEBUG) if
* all is not as expected ...
*/
ASSERT(status == 0xCC);
ASSERT(ssp->hw_int_enabled);
return (rslt);
}
#endif /* 0 */
/*
* Enable/disable interrupts
*/
static void
lombus_set_irq(struct lombus_state *ssp, boolean_t newstate)
{
uint8_t val;
val = newstate ? SIO_IER_RXHDL_IE : 0;
sio_put_reg(ssp, SIO_IER, SIO_IER_STD | val);
ssp->hw_int_enabled = newstate;
}
/*
* Assert/deassert RTS
*/
static void
lombus_toggle_rts(struct lombus_state *ssp)
{
uint8_t val;
val = sio_get_reg(ssp, SIO_MCR);
val &= SIO_MCR_RTS;
val ^= SIO_MCR_RTS;
val |= SIO_MCR_STD;
sio_put_reg(ssp, SIO_MCR, val);
}
/*
* High-level interrupt handler:
* Checks whether initialisation is complete (to avoid a race
* with mutex_init()), and whether chip interrupts are enabled.
* If not, the interrupt's not for us, so just return UNCLAIMED.
* Otherwise, disable the interrupt, trigger a softint, and return
* CLAIMED. The softint handler will then do all the real work.
*
* NOTE: the chip interrupt capability is only re-enabled once the
* receive code has run, but that can be called from a poll loop
* or cyclic callback as well as from the softint. So it's *not*
* guaranteed that there really is a chip interrupt pending here,
* 'cos the work may already have been done and the reason for the
* interrupt gone away before we get here.
*
* OTOH, if we come through here twice without the receive code
* having run in between, that's definitely wrong. In such an
* event, we would notice that chip interrupts haven't yet been
* re-enabled and return UNCLAIMED, allowing the system's jabber
* protect code (if any) to do its job.
*/
static uint_t
lombus_hi_intr(caddr_t arg)
{
struct lombus_state *ssp = (void *)arg;
uint_t claim;
claim = DDI_INTR_UNCLAIMED;
if (ssp->cycid != NULL) {
mutex_enter(ssp->hw_mutex);
if (ssp->hw_int_enabled) {
lombus_set_irq(ssp, B_FALSE);
ddi_trigger_softintr(ssp->softid);
claim = DDI_INTR_CLAIMED;
}
mutex_exit(ssp->hw_mutex);
}
return (claim);
}
/*
* Packet receive handler
*
* This routine should be called from the low-level softint, or the
* cyclic callback, or lombus_cmd() (for polled operation), with the
* low-level mutex already held.
*/
static void
lombus_receive(struct lombus_state *ssp)
{
boolean_t ready = B_FALSE;
uint8_t data = 0;
uint8_t rcvd = 0;
uint8_t tmp;
lombus_trace(ssp, 'S', "lombus_receive",
"state %d; error $%x",
ssp->cmdstate, ssp->error);
/*
* Check for access faults before starting the receive
* loop (we don't want to cause bus errors or suchlike
* unpleasantness in the event that the SIO has died).
*/
if (!sio_faulty(ssp)) {
/*
* Read bytes from the FIFO until they're all gone,
* or we find the 'END OF PACKET' set on one, or
* our buffer overflows (which must be an error)
*/
mutex_enter(ssp->hw_mutex);
while (sio_data_ready(ssp)) {
data = sio_get_reg(ssp, SIO_RXD);
ssp->reply[rcvd = ssp->index] = data;
if (++rcvd >= LOMBUS_BUFSIZE)
break;
ssp->index = rcvd;
if (data & LOMBUS_LAST)
break;
}
lombus_set_irq(ssp, B_TRUE);
mutex_exit(ssp->hw_mutex);
}
lombus_trace(ssp, 'S', "lombus_receive",
"rcvd %d: $%02x $%02x $%02x $%02x $%02x $%02x $%02x $%02x",
rcvd,
ssp->reply[0], ssp->reply[1],
ssp->reply[2], ssp->reply[3],
ssp->reply[4], ssp->reply[5],
ssp->reply[6], ssp->reply[7]);
if (ssp->cmdstate != LOMBUS_CMDSTATE_WAITING) {
/*
* We're not expecting any data in this state, so if
* we DID receive any data, we just throw it away by
* resetting the buffer index to 0.
*/
ssp->index = 0;
} else if (rcvd == 0) {
/*
* No bytes received this time through (though there
* might be a partial packet sitting in the buffer).
* If it seems the LOM is taking too long to respond,
* we'll assume it's died and return an error.
*/
if (ddi_get_lbolt() > ssp->deadline) {
ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
ssp->error = LOMBUS_ERR_TIMEOUT;
ready = B_TRUE;
}
} else if (rcvd >= LOMBUS_BUFSIZE) {
/*
* Buffer overflow; discard the data & treat as an error
* (even if the last byte read did claim to terminate a
* packet, it can't be a valid one 'cos it's too long!)
*/
ssp->index = 0;
ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
ssp->error = LOMBUS_ERR_OFLOW;
ready = B_TRUE;
} else if ((data & LOMBUS_LAST) == 0) {
/*
* Packet not yet complete; leave the partial packet in
* the buffer for later ...
*/
_NOTE(EMPTY)
;
} else if ((data & LOMBUS_MASK) != LOMBUS_STATUS) {
/*
* Invalid "status" byte - maybe an echo of the command?
*
* As a debugging feature, we allow for this, assuming
* that if the LOM has echoed the command byte, it has
* also echoed all the parameter bytes before starting
* command processing. So, we dump out the buffer and
* then clear it, so we can go back to looking for the
* real reply.
*
* Otherwise, we just drop the data & flag an error.
*/
if (ssp->allow_echo) {
lombus_trace(ssp, 'E', "lombus_receive",
"echo $%02x $%02x $%02x $%02x "
"$%02x $%02x $%02x $%02x",
ssp->reply[0], ssp->reply[1],
ssp->reply[2], ssp->reply[3],
ssp->reply[4], ssp->reply[5],
ssp->reply[6], ssp->reply[7]);
ssp->index = 0;
} else {
ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
ssp->error = LOMBUS_ERR_BADSTATUS;
ready = B_TRUE;
}
} else if ((data & LOMBUS_SEQ) != ssp->sequence) {
/*
* Wrong sequence number! Flag this as an error
*/
ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
ssp->error = LOMBUS_ERR_SEQUENCE;
ready = B_TRUE;
} else {
/*
* Finally, we know that's it's a valid reply to our
* last command. Update the ASYNC status, derive the
* reply parameter (if any), and check the ERROR bit
* to find out what the parameter means.
*
* Note that not all the values read/assigned here
* are meaningful, but it doesn't matter; the waiting
* thread will know which one(s) it should check.
*/
ssp->async = (data & LOMBUS_STATUS_ASYNC) ? 1 : 0;
tmp = ((data & LOMBUS_STATUS_MSB) ? 0x80 : 0) | ssp->reply[0];
if (data & LOMBUS_STATUS_ERR) {
ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
ssp->error = tmp;
} else {
ssp->cmdstate = LOMBUS_CMDSTATE_READY;
ssp->result = tmp;
}
ready = B_TRUE;
}
lombus_trace(ssp, 'T', "lombus_receive",
"rcvd %d; last $%02x; state %d; error $%x; ready %d",
rcvd, data, ssp->cmdstate, ssp->error, ready);
if (ready)
cv_broadcast(ssp->lo_cv);
}
/*
* Low-level softint handler
*
* This routine should be triggered whenever there's a byte to be read
*/
static uint_t
lombus_softint(caddr_t arg)
{
struct lombus_state *ssp = (void *)arg;
mutex_enter(ssp->lo_mutex);
lombus_receive(ssp);
mutex_exit(ssp->lo_mutex);
return (DDI_INTR_CLAIMED);
}
/*
* Cyclic handler: just calls the receive routine, in case interrupts
* are not being delivered and in order to handle command timeout
*/
static void
lombus_cyclic(void *arg)
{
struct lombus_state *ssp = (void *)arg;
mutex_enter(ssp->lo_mutex);
lombus_receive(ssp);
mutex_exit(ssp->lo_mutex);
}
/*
* Serial protocol
*
* This routine builds a command and sets it in progress.
*/
static uint8_t
lombus_cmd(HANDLE_TYPE *hdlp, ptrdiff_t vreg, uint_t val, uint_t cmd)
{
struct lombus_state *ssp;
clock_t start;
uint8_t *p;
/*
* First of all, wait for the interface to be available.
*
* NOTE: we blow through all the mutex/cv/state checking and
* preempt any command in progress if the system is panicking!
*/
ssp = HANDLE_PRIVATE(hdlp);
mutex_enter(ssp->lo_mutex);
while (ssp->cmdstate != LOMBUS_CMDSTATE_IDLE && !panicstr)
cv_wait(ssp->lo_cv, ssp->lo_mutex);
ssp->cmdstate = LOMBUS_CMDSTATE_BUSY;
ssp->sequence = (ssp->sequence + LOMBUS_SEQ_LSB) & LOMBUS_SEQ;
/*
* We have exclusive ownership, so assemble the command (backwards):
*
* [byte 0] Command: modified by XADDR and/or WMSB bits
* [Optional] Parameter: Value to write (low 7 bits)
* [Optional] Parameter: Register number (high 7 bits)
* [Optional] Parameter: Register number (low 7 bits)
*/
p = &ssp->cmdbuf[0];
*p++ = LOMBUS_CMD | ssp->sequence | cmd;
switch (cmd) {
case LOMBUS_CMD_WRITE:
*p++ = val & 0x7f;
if (val >= 0x80)
ssp->cmdbuf[0] |= LOMBUS_CMD_WMSB;
/*FALLTHRU*/
case LOMBUS_CMD_READ:
if (LOMBUS_VREG_HI(vreg) != 0) {
*p++ = LOMBUS_VREG_HI(vreg);
ssp->cmdbuf[0] |= LOMBUS_CMD_XADDR;
}
*p++ = LOMBUS_VREG_LO(vreg);
/*FALLTHRU*/
case LOMBUS_CMD_NOP:
break;
}
/*
* Check and update the SIO h/w fault status before accessing
* the chip registers. If there's a (new or previous) fault,
* we'll run through the protocol but won't really touch the
* hardware and all commands will timeout. If a previously
* discovered fault has now gone away (!), then we can (try to)
* proceed with the new command (probably a probe).
*/
sio_check_fault_status(ssp);
/*
* Wait up to LOMBUS_CTS_TIMEOUT (2 seconds) for the LOM to tell
* us that it's ready for the next command. If it doesn't, though,
* we'll send it anyway, on the basis that the CTS signal might be
* open- or short-circuited (or the LOM firmware forgot to set it,
* or the LOM just got reset, or whatever ...)
*/
start = ddi_get_lbolt();
ssp->deadline = start + drv_usectohz(LOMBUS_CTS_TIMEOUT/1000);
while (!sio_lom_ready(ssp)) {
if (ddi_get_lbolt() > ssp->deadline)
break;
(void) cv_reltimedwait(ssp->lo_cv, ssp->lo_mutex,
drv_usectohz(LOMBUS_CTS_POLL/1000), TR_CLOCK_TICK);
}
/*
* Either the LOM is ready, or we timed out waiting for CTS.
* In either case, we're going to send the command now by
* stuffing the packet into the Tx FIFO, reversing it as we go.
* We call lombus_receive() first to ensure there isn't any
* garbage left in the Rx FIFO from an earlier command that
* timed out (or was pre-empted by a PANIC!). This also makes
* sure that SIO interrupts are enabled so we'll see the reply
* more quickly (the poll loop below will still work even if
* interrupts aren't enabled, but it will take longer).
*/
lombus_receive(ssp);
mutex_enter(ssp->hw_mutex);
while (p > ssp->cmdbuf)
sio_put_reg(ssp, SIO_TXD, *--p);
mutex_exit(ssp->hw_mutex);
/*
* Prepare for the reply (to be processed by the interrupt/cyclic
* handler and/or polling loop below), then wait for a response
* or timeout.
*/
start = ddi_get_lbolt();
ssp->deadline = start + drv_usectohz(LOMBUS_CMD_TIMEOUT/1000);
ssp->error = 0;
ssp->index = 0;
ssp->result = DUMMY_VALUE;
ssp->cmdstate = LOMBUS_CMDSTATE_WAITING;
while (ssp->cmdstate == LOMBUS_CMDSTATE_WAITING) {
if (cv_reltimedwait(ssp->lo_cv, ssp->lo_mutex,
drv_usectohz(LOMBUS_CMD_POLL/1000), TR_CLOCK_TICK) == -1)
lombus_receive(ssp);
}
/*
* The return value may not be meaningful but retrieve it anyway
*/
val = ssp->result;
if (sio_faulty(ssp)) {
val = DUMMY_VALUE;
HANDLE_FAULT(hdlp) = LOMBUS_ERR_SIOHW;
} else if (ssp->cmdstate != LOMBUS_CMDSTATE_READY) {
/*
* Some problem here ... transfer the error code from
* the per-instance state to the per-handle fault flag.
* The error code shouldn't be zero!
*/
if (ssp->error != 0)
HANDLE_FAULT(hdlp) = ssp->error;
else
HANDLE_FAULT(hdlp) = LOMBUS_ERR_BADERRCODE;
}
/*
* All done now!
*/
ssp->index = 0;
ssp->cmdstate = LOMBUS_CMDSTATE_IDLE;
cv_broadcast(ssp->lo_cv);
mutex_exit(ssp->lo_mutex);
return (val);
}
/*
* Space 0 - LOM virtual register access
* Only 8-bit accesses are supported.
*/
static uint8_t
lombus_vreg_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
ptrdiff_t offset;
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return (DUMMY_VALUE);
}
/*
* Derive the virtual register number and run the command
*/
return (lombus_cmd(hdlp, ADDR_TO_VREG(addr), 0, LOMBUS_CMD_READ));
}
static void
lombus_vreg_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
ptrdiff_t offset;
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return;
}
/*
* Derive the virtual register number and run the command
*/
(void) lombus_cmd(hdlp, ADDR_TO_VREG(addr), val, LOMBUS_CMD_WRITE);
}
static void
lombus_vreg_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
*host_addr++ = lombus_vreg_get8(hdlp, dev_addr);
}
static void
lombus_vreg_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
lombus_vreg_put8(hdlp, dev_addr, *host_addr++);
}
/*
* Space 1 - LOM watchdog pat register access
* Only 8-bit accesses are supported.
*
* Reads have no effect and return 0.
*
* Writes pat the dog by toggling the RTS line iff enough time has
* elapsed since last time we toggled it.
*
* Multi-byte reads (using ddi_rep_get8(9F)) are a fairly inefficient
* way of zeroing the destination area ;-) and still won't pat the dog.
*
* Multi-byte writes (using ddi_rep_put8(9F)) will almost certainly
* only count as a single pat, no matter how many bytes the caller
* says to write, as the inter-pat time is VERY long compared with
* the time it will take to read the memory source area.
*/
static uint8_t
lombus_pat_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
ptrdiff_t offset;
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return (DUMMY_VALUE);
}
return (0);
}
static void
lombus_pat_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
struct lombus_state *ssp;
ptrdiff_t offset;
hrtime_t now;
_NOTE(ARGUNUSED(val))
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return;
}
ssp = HANDLE_PRIVATE(hdlp);
mutex_enter(ssp->hw_mutex);
now = gethrtime();
if ((now - ssp->hw_last_pat) >= LOMBUS_MIN_PAT) {
lombus_toggle_rts(ssp);
ssp->hw_last_pat = now;
}
mutex_exit(ssp->hw_mutex);
}
static void
lombus_pat_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
*host_addr++ = lombus_pat_get8(hdlp, dev_addr);
}
static void
lombus_pat_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
lombus_pat_put8(hdlp, dev_addr, *host_addr++);
}
/*
* Space 2 - LOM async event flag register access
* Only 16-bit accesses are supported.
*/
static uint16_t
lombus_event_get16(HANDLE_TYPE *hdlp, uint16_t *addr)
{
struct lombus_state *ssp;
ptrdiff_t offset;
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping orignally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return (DUMMY_VALUE);
}
/*
* Return the value of the asynchronous-event-pending flag
* as passed back by the LOM at the end of the last command.
*/
ssp = HANDLE_PRIVATE(hdlp);
return (ssp->async);
}
static void
lombus_event_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val)
{
ptrdiff_t offset;
_NOTE(ARGUNUSED(val))
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return;
}
/*
* The user can't overwrite the asynchronous-event-pending flag!
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_RO;
}
static void
lombus_event_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
*host_addr++ = lombus_event_get16(hdlp, dev_addr);
}
static void
lombus_event_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
lombus_event_put16(hdlp, dev_addr, *host_addr++);
}
/*
* All spaces - access handle fault information
* Only 32-bit accesses are supported.
*/
static uint32_t
lombus_meta_get32(HANDLE_TYPE *hdlp, uint32_t *addr)
{
struct lombus_state *ssp;
ptrdiff_t offset;
/*
* Derive the offset that the caller has added to the base
* address originally returned, and use it to determine
* which meta-register is to be accessed ...
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
switch (offset) {
case LOMBUS_FAULT_REG:
/*
* This meta-register provides a code for the most
* recent virtual register access fault, if any.
*/
return (HANDLE_FAULT(hdlp));
case LOMBUS_PROBE_REG:
/*
* Reading this meta-register clears any existing fault
* (at the virtual, not the hardware access layer), then
* runs a NOP command and returns the fault code from that.
*/
HANDLE_FAULT(hdlp) = 0;
(void) lombus_cmd(hdlp, 0, 0, LOMBUS_CMD_NOP);
return (HANDLE_FAULT(hdlp));
case LOMBUS_ASYNC_REG:
/*
* Obsolescent - but still supported for backwards
* compatibility. This is an alias for the newer
* LOMBUS_EVENT_REG, but doesn't require a separate
* "reg" entry and ddi_regs_map_setup() call.
*
* It returns the value of the asynchronous-event-pending
* flag as passed back by the LOM at the end of the last
* completed command.
*/
ssp = HANDLE_PRIVATE(hdlp);
return (ssp->async);
default:
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return (DUMMY_VALUE);
}
}
static void
lombus_meta_put32(HANDLE_TYPE *hdlp, uint32_t *addr, uint32_t val)
{
ptrdiff_t offset;
/*
* Derive the offset that the caller has added to the base
* address originally returned, and use it to determine
* which meta-register is to be accessed ...
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
switch (offset) {
case LOMBUS_FAULT_REG:
/*
* This meta-register contains a code for the most
* recent virtual register access fault, if any.
* It can be cleared simply by writing 0 to it.
*/
HANDLE_FAULT(hdlp) = val;
return;
case LOMBUS_PROBE_REG:
/*
* Writing this meta-register clears any existing fault
* (at the virtual, not the hardware acess layer), then
* runs a NOP command. The caller can check the fault
* code later if required.
*/
HANDLE_FAULT(hdlp) = 0;
(void) lombus_cmd(hdlp, 0, 0, LOMBUS_CMD_NOP);
return;
default:
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return;
}
}
static void
lombus_meta_rep_get32(HANDLE_TYPE *hdlp, uint32_t *host_addr,
uint32_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
*host_addr++ = lombus_meta_get32(hdlp, dev_addr);
}
static void
lombus_meta_rep_put32(HANDLE_TYPE *hdlp, uint32_t *host_addr,
uint32_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
lombus_meta_put32(hdlp, dev_addr, *host_addr++);
}
/*
* Finally, some dummy functions for all unsupported access
* space/size/mode combinations ...
*/
static uint8_t
lombus_no_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
_NOTE(ARGUNUSED(addr))
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return (DUMMY_VALUE);
}
static void
lombus_no_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
_NOTE(ARGUNUSED(addr, val))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
lombus_no_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
lombus_no_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static uint16_t
lombus_no_get16(HANDLE_TYPE *hdlp, uint16_t *addr)
{
_NOTE(ARGUNUSED(addr))
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return (DUMMY_VALUE);
}
static void
lombus_no_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val)
{
_NOTE(ARGUNUSED(addr, val))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
lombus_no_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
lombus_no_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static uint64_t
lombus_no_get64(HANDLE_TYPE *hdlp, uint64_t *addr)
{
_NOTE(ARGUNUSED(addr))
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return (DUMMY_VALUE);
}
static void
lombus_no_put64(HANDLE_TYPE *hdlp, uint64_t *addr, uint64_t val)
{
_NOTE(ARGUNUSED(addr, val))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
lombus_no_rep_get64(HANDLE_TYPE *hdlp, uint64_t *host_addr,
uint64_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
lombus_no_rep_put64(HANDLE_TYPE *hdlp, uint64_t *host_addr,
uint64_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static int
lombus_acc_fault_check(HANDLE_TYPE *hdlp)
{
return (HANDLE_FAULT(hdlp) != 0);
}
/*
* Hardware setup - put the SIO chip in the required operational
* state, with all our favourite parameters programmed correctly.
* This routine leaves all SIO interrupts disabled.
*/
static void
lombus_hw_reset(struct lombus_state *ssp)
{
uint16_t divisor;
/*
* Disable interrupts, soft reset Tx and Rx circuitry,
* reselect standard modes (bits/char, parity, etc).
*/
lombus_set_irq(ssp, B_FALSE);
sio_put_reg(ssp, SIO_FCR, SIO_FCR_RXSR | SIO_FCR_TXSR);
sio_put_reg(ssp, SIO_LCR, SIO_LCR_STD);
/*
* Select the proper baud rate; if the value is invalid
* (presumably 0, i.e. not specified, but also if the
* "baud" property is set to some silly value), we assume
* the default.
*/
if (ssp->baud < SIO_BAUD_MIN || ssp->baud > SIO_BAUD_MAX)
divisor = SIO_BAUD_TO_DIVISOR(SIO_BAUD_DEFAULT);
else
divisor = SIO_BAUD_TO_DIVISOR(ssp->baud);
/*
* According to the datasheet, it is forbidden for the divisor
* register to be zero. So when loading the register in two
* steps, we have to make sure that the temporary value formed
* between loads is nonzero. However, we can't rely on either
* half already having a nonzero value, as the datasheet also
* says that these registers are indeterminate after a reset!
* So, we explicitly set the low byte to a non-zero value first;
* then we can safely load the high byte, and then the correct
* value for the low byte, without the result ever being zero.
*/
sio_put_reg(ssp, SIO_BSR, SIO_BSR_BANK1);
sio_put_reg(ssp, SIO_LBGDL, 0xff);
sio_put_reg(ssp, SIO_LBGDH, divisor >> 8);
sio_put_reg(ssp, SIO_LBGDL, divisor & 0xff);
sio_put_reg(ssp, SIO_BSR, SIO_BSR_BANK0);
/*
* Program the remaining device registers as required
*/
sio_put_reg(ssp, SIO_MCR, SIO_MCR_STD);
sio_put_reg(ssp, SIO_FCR, SIO_FCR_STD);
}
/*
* Higher-level setup & teardown
*/
static void
lombus_offline(struct lombus_state *ssp)
{
if (ssp->sio_handle != NULL)
ddi_regs_map_free(&ssp->sio_handle);
ssp->sio_handle = NULL;
ssp->sio_regs = NULL;
}
static int
lombus_online(struct lombus_state *ssp)
{
ddi_acc_handle_t h;
caddr_t p;
int nregs;
int err;
if (ddi_dev_nregs(ssp->dip, &nregs) != DDI_SUCCESS)
nregs = 0;
switch (nregs) {
default:
case 1:
/*
* regset 0 represents the SIO operating registers
*/
err = ddi_regs_map_setup(ssp->dip, 0, &p, 0, 0,
lombus_dev_acc_attr, &h);
lombus_trace(ssp, 'O', "online",
"regmap 0 status %d addr $%p", err, p);
if (err != DDI_SUCCESS)
return (EIO);
ssp->sio_handle = h;
ssp->sio_regs = (void *)p;
break;
case 0:
/*
* If no registers are defined, succeed vacuously;
* commands will be accepted, but we fake the accesses.
*/
break;
}
/*
* Now that the registers are mapped, we can initialise the SIO h/w
*/
lombus_hw_reset(ssp);
return (0);
}
/*
* Nexus routines
*/
#if defined(NDI_ACC_HDL_V2)
static const ndi_acc_fns_t lombus_vreg_acc_fns = {
NDI_ACC_FNS_CURRENT,
NDI_ACC_FNS_V1,
lombus_vreg_get8,
lombus_vreg_put8,
lombus_vreg_rep_get8,
lombus_vreg_rep_put8,
lombus_no_get16,
lombus_no_put16,
lombus_no_rep_get16,
lombus_no_rep_put16,
lombus_meta_get32,
lombus_meta_put32,
lombus_meta_rep_get32,
lombus_meta_rep_put32,
lombus_no_get64,
lombus_no_put64,
lombus_no_rep_get64,
lombus_no_rep_put64,
lombus_acc_fault_check
};
static const ndi_acc_fns_t lombus_pat_acc_fns = {
NDI_ACC_FNS_CURRENT,
NDI_ACC_FNS_V1,
lombus_pat_get8,
lombus_pat_put8,
lombus_pat_rep_get8,
lombus_pat_rep_put8,
lombus_no_get16,
lombus_no_put16,
lombus_no_rep_get16,
lombus_no_rep_put16,
lombus_meta_get32,
lombus_meta_put32,
lombus_meta_rep_get32,
lombus_meta_rep_put32,
lombus_no_get64,
lombus_no_put64,
lombus_no_rep_get64,
lombus_no_rep_put64,
lombus_acc_fault_check
};
static const ndi_acc_fns_t lombus_event_acc_fns = {
NDI_ACC_FNS_CURRENT,
NDI_ACC_FNS_V1,
lombus_no_get8,
lombus_no_put8,
lombus_no_rep_get8,
lombus_no_rep_put8,
lombus_event_get16,
lombus_event_put16,
lombus_event_rep_get16,
lombus_event_rep_put16,
lombus_meta_get32,
lombus_meta_put32,
lombus_meta_rep_get32,
lombus_meta_rep_put32,
lombus_no_get64,
lombus_no_put64,
lombus_no_rep_get64,
lombus_no_rep_put64,
lombus_acc_fault_check
};
static int
lombus_map_handle(struct lombus_state *ssp, ddi_map_op_t op,
int space, caddr_t vaddr, off_t len,
ndi_acc_handle_t *hdlp, caddr_t *addrp)
{
switch (op) {
default:
return (DDI_ME_UNIMPLEMENTED);
case DDI_MO_MAP_LOCKED:
switch (space) {
default:
return (DDI_ME_REGSPEC_RANGE);
case LOMBUS_VREG_SPACE:
ndi_set_acc_fns(hdlp, &lombus_vreg_acc_fns);
break;
case LOMBUS_PAT_SPACE:
ndi_set_acc_fns(hdlp, &lombus_pat_acc_fns);
break;
case LOMBUS_EVENT_SPACE:
ndi_set_acc_fns(hdlp, &lombus_event_acc_fns);
break;
}
hdlp->ah_addr = *addrp = vaddr;
hdlp->ah_len = len;
hdlp->ah_bus_private = ssp;
return (DDI_SUCCESS);
case DDI_MO_UNMAP:
*addrp = NULL;
hdlp->ah_bus_private = NULL;
return (DDI_SUCCESS);
}
}
#else
static int
lombus_map_handle(struct lombus_state *ssp, ddi_map_op_t op,
int space, caddr_t vaddr, off_t len,
ddi_acc_hdl_t *hdlp, caddr_t *addrp)
{
ddi_acc_impl_t *aip = hdlp->ah_platform_private;
switch (op) {
default:
return (DDI_ME_UNIMPLEMENTED);
case DDI_MO_MAP_LOCKED:
switch (space) {
default:
return (DDI_ME_REGSPEC_RANGE);
case LOMBUS_VREG_SPACE:
aip->ahi_get8 = lombus_vreg_get8;
aip->ahi_put8 = lombus_vreg_put8;
aip->ahi_rep_get8 = lombus_vreg_rep_get8;
aip->ahi_rep_put8 = lombus_vreg_rep_put8;
aip->ahi_get16 = lombus_no_get16;
aip->ahi_put16 = lombus_no_put16;
aip->ahi_rep_get16 = lombus_no_rep_get16;
aip->ahi_rep_put16 = lombus_no_rep_put16;
aip->ahi_get32 = lombus_meta_get32;
aip->ahi_put32 = lombus_meta_put32;
aip->ahi_rep_get32 = lombus_meta_rep_get32;
aip->ahi_rep_put32 = lombus_meta_rep_put32;
aip->ahi_get64 = lombus_no_get64;
aip->ahi_put64 = lombus_no_put64;
aip->ahi_rep_get64 = lombus_no_rep_get64;
aip->ahi_rep_put64 = lombus_no_rep_put64;
aip->ahi_fault_check = lombus_acc_fault_check;
break;
case LOMBUS_PAT_SPACE:
aip->ahi_get8 = lombus_pat_get8;
aip->ahi_put8 = lombus_pat_put8;
aip->ahi_rep_get8 = lombus_pat_rep_get8;
aip->ahi_rep_put8 = lombus_pat_rep_put8;
aip->ahi_get16 = lombus_no_get16;
aip->ahi_put16 = lombus_no_put16;
aip->ahi_rep_get16 = lombus_no_rep_get16;
aip->ahi_rep_put16 = lombus_no_rep_put16;
aip->ahi_get32 = lombus_meta_get32;
aip->ahi_put32 = lombus_meta_put32;
aip->ahi_rep_get32 = lombus_meta_rep_get32;
aip->ahi_rep_put32 = lombus_meta_rep_put32;
aip->ahi_get64 = lombus_no_get64;
aip->ahi_put64 = lombus_no_put64;
aip->ahi_rep_get64 = lombus_no_rep_get64;
aip->ahi_rep_put64 = lombus_no_rep_put64;
aip->ahi_fault_check = lombus_acc_fault_check;
break;
case LOMBUS_EVENT_SPACE:
aip->ahi_get8 = lombus_no_get8;
aip->ahi_put8 = lombus_no_put8;
aip->ahi_rep_get8 = lombus_no_rep_get8;
aip->ahi_rep_put8 = lombus_no_rep_put8;
aip->ahi_get16 = lombus_event_get16;
aip->ahi_put16 = lombus_event_put16;
aip->ahi_rep_get16 = lombus_event_rep_get16;
aip->ahi_rep_put16 = lombus_event_rep_put16;
aip->ahi_get32 = lombus_meta_get32;
aip->ahi_put32 = lombus_meta_put32;
aip->ahi_rep_get32 = lombus_meta_rep_get32;
aip->ahi_rep_put32 = lombus_meta_rep_put32;
aip->ahi_get64 = lombus_no_get64;
aip->ahi_put64 = lombus_no_put64;
aip->ahi_rep_get64 = lombus_no_rep_get64;
aip->ahi_rep_put64 = lombus_no_rep_put64;
aip->ahi_fault_check = lombus_acc_fault_check;
break;
}
hdlp->ah_addr = *addrp = vaddr;
hdlp->ah_len = len;
hdlp->ah_bus_private = ssp;
return (DDI_SUCCESS);
case DDI_MO_UNMAP:
*addrp = NULL;
hdlp->ah_bus_private = NULL;
return (DDI_SUCCESS);
}
}
#endif /* NDI_ACC_HDL_V2 */
static int
lombus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
off_t off, off_t len, caddr_t *addrp)
{
struct lombus_child_info *lcip;
struct lombus_state *ssp;
lombus_regspec_t *rsp;
if ((ssp = lombus_getstate(dip, -1, "lombus_map")) == NULL)
return (DDI_FAILURE); /* this "can't happen" */
/*
* Validate mapping request ...
*/
if (mp->map_flags != DDI_MF_KERNEL_MAPPING)
return (DDI_ME_UNSUPPORTED);
if (mp->map_handlep == NULL)
return (DDI_ME_UNSUPPORTED);
if (mp->map_type != DDI_MT_RNUMBER)
return (DDI_ME_UNIMPLEMENTED);
if ((lcip = ddi_get_parent_data(rdip)) == NULL)
return (DDI_ME_INVAL);
if ((rsp = lcip->rsp) == NULL)
return (DDI_ME_INVAL);
if (mp->map_obj.rnumber >= lcip->nregs)
return (DDI_ME_RNUMBER_RANGE);
rsp += mp->map_obj.rnumber;
if (off < 0 || off >= rsp->lombus_size)
return (DDI_ME_INVAL);
if (len == 0)
len = rsp->lombus_size-off;
if (len < 0)
return (DDI_ME_INVAL);
if (off+len < 0 || off+len > rsp->lombus_size)
return (DDI_ME_INVAL);
return (lombus_map_handle(ssp, mp->map_op,
rsp->lombus_space, VREG_TO_ADDR(rsp->lombus_base+off), len,
mp->map_handlep, addrp));
}
static int
lombus_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op,
void *arg, void *result)
{
struct lombus_child_info *lcip;
struct lombus_state *ssp;
lombus_regspec_t *rsp;
dev_info_t *cdip;
char addr[32];
uint_t nregs;
uint_t rnum;
int *regs;
int limit;
int err;
int i;
if ((ssp = lombus_getstate(dip, -1, "lombus_ctlops")) == NULL)
return (DDI_FAILURE); /* this "can't happen" */
switch (op) {
default:
break;
case DDI_CTLOPS_INITCHILD:
/*
* First, look up and validate the "reg" property.
*
* It must be a non-empty integer array containing a set
* of triples. Once we've verified that, we can treat it
* as an array of type lombus_regspec_t[], which defines
* the meaning of the elements of each triple:
* + the first element of each triple must be a valid space
* + the second and third elements (base, size) of each
* triple must define a valid subrange of that space
* If it passes all the tests, we save it away for future
* reference in the child's parent-private-data field.
*/
cdip = arg;
err = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip,
DDI_PROP_DONTPASS, "reg", &regs, &nregs);
lombus_trace(ssp, 'C', "initchild",
"prop status %d size %d", err, nregs);
if (err != DDI_PROP_SUCCESS)
return (DDI_FAILURE);
err = (nregs <= 0 || (nregs % LOMBUS_REGSPEC_SIZE) != 0);
nregs /= LOMBUS_REGSPEC_SIZE;
rsp = (lombus_regspec_t *)regs;
for (i = 0; i < nregs && !err; ++i) {
switch (rsp[i].lombus_space) {
default:
limit = 0;
err = 1;
break;
case LOMBUS_VREG_SPACE:
limit = LOMBUS_MAX_REG+1;
break;
case LOMBUS_PAT_SPACE:
limit = LOMBUS_PAT_REG+1;
break;
case LOMBUS_EVENT_SPACE:
limit = LOMBUS_EVENT_REG+1;
break;
}
err |= (rsp[i].lombus_base < 0);
err |= (rsp[i].lombus_base >= limit);
if (rsp[i].lombus_size == 0)
rsp[i].lombus_size = limit-rsp[i].lombus_base;
err |= (rsp[i].lombus_size < 0);
err |= (rsp[i].lombus_base+rsp[i].lombus_size < 0);
err |= (rsp[i].lombus_base+rsp[i].lombus_size > limit);
}
if (err) {
ddi_prop_free(regs);
return (DDI_FAILURE);
}
lcip = kmem_zalloc(sizeof (*lcip), KM_SLEEP);
lcip->nregs = nregs;
lcip->rsp = rsp;
ddi_set_parent_data(cdip, lcip);
(void) snprintf(addr, sizeof (addr),
"%x,%x", rsp[0].lombus_space, rsp[0].lombus_base);
ddi_set_name_addr(cdip, addr);
return (DDI_SUCCESS);
case DDI_CTLOPS_UNINITCHILD:
cdip = arg;
ddi_set_name_addr(cdip, NULL);
lcip = ddi_get_parent_data(cdip);
ddi_set_parent_data(cdip, NULL);
ddi_prop_free(lcip->rsp);
kmem_free(lcip, sizeof (*lcip));
return (DDI_SUCCESS);
case DDI_CTLOPS_REPORTDEV:
if (rdip == NULL)
return (DDI_FAILURE);
cmn_err(CE_CONT, "?LOM device: %s@%s, %s#%d\n",
ddi_node_name(rdip), ddi_get_name_addr(rdip),
ddi_driver_name(dip), ddi_get_instance(dip));
return (DDI_SUCCESS);
case DDI_CTLOPS_REGSIZE:
if ((lcip = ddi_get_parent_data(rdip)) == NULL)
return (DDI_FAILURE);
if ((rnum = *(uint_t *)arg) >= lcip->nregs)
return (DDI_FAILURE);
*(off_t *)result = lcip->rsp[rnum].lombus_size;
return (DDI_SUCCESS);
case DDI_CTLOPS_NREGS:
if ((lcip = ddi_get_parent_data(rdip)) == NULL)
return (DDI_FAILURE);
*(int *)result = lcip->nregs;
return (DDI_SUCCESS);
}
return (ddi_ctlops(dip, rdip, op, arg, result));
}
/*
* Clean up on detach or failure of attach
*/
static int
lombus_unattach(struct lombus_state *ssp, int instance)
{
if (ssp != NULL) {
lombus_hw_reset(ssp);
if (ssp->cycid != NULL) {
ddi_periodic_delete(ssp->cycid);
ssp->cycid = NULL;
if (ssp->sio_handle != NULL)
ddi_remove_intr(ssp->dip, 0, ssp->hw_iblk);
ddi_remove_softintr(ssp->softid);
cv_destroy(ssp->lo_cv);
mutex_destroy(ssp->lo_mutex);
mutex_destroy(ssp->hw_mutex);
}
lombus_offline(ssp);
ddi_set_driver_private(ssp->dip, NULL);
}
ddi_soft_state_free(lombus_statep, instance);
return (DDI_FAILURE);
}
/*
* Autoconfiguration routines
*/
static int
lombus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
struct lombus_state *ssp = NULL;
int instance;
int err;
switch (cmd) {
default:
return (DDI_FAILURE);
case DDI_ATTACH:
break;
}
/*
* Allocate the soft-state structure
*/
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(lombus_statep, instance) != DDI_SUCCESS)
return (DDI_FAILURE);
if ((ssp = lombus_getstate(dip, instance, "lombus_attach")) == NULL)
return (lombus_unattach(ssp, instance));
ddi_set_driver_private(dip, ssp);
/*
* Initialise devinfo-related fields
*/
ssp->dip = dip;
ssp->majornum = ddi_driver_major(dip);
ssp->instance = instance;
/*
* Set various options from .conf properties
*/
ssp->allow_echo = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "allow-lom-echo", 0) != 0;
ssp->baud = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "baud-rate", 0);
ssp->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "debug", 0);
ssp->fake_cts = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "fake-cts", 0) != 0;
/*
* Initialise current state & time
*/
ssp->cmdstate = LOMBUS_CMDSTATE_IDLE;
ssp->hw_last_pat = gethrtime();
ssp->cycid = NULL;
/*
* Online the hardware ...
*/
err = lombus_online(ssp);
if (err != 0)
return (lombus_unattach(ssp, instance));
/*
* Install soft and hard interrupt handler(s)
* Initialise mutexes and cv
* Start cyclic callbacks
* Enable interrupts
*/
err = ddi_add_softintr(dip, DDI_SOFTINT_LOW, &ssp->softid,
&ssp->lo_iblk, NULL, lombus_softint, (caddr_t)ssp);
if (err != DDI_SUCCESS)
return (lombus_unattach(ssp, instance));
if (ssp->sio_handle != NULL)
err = ddi_add_intr(dip, 0, &ssp->hw_iblk, NULL,
lombus_hi_intr, (caddr_t)ssp);
mutex_init(ssp->hw_mutex, NULL, MUTEX_DRIVER, ssp->hw_iblk);
mutex_init(ssp->lo_mutex, NULL, MUTEX_DRIVER, ssp->lo_iblk);
cv_init(ssp->lo_cv, NULL, CV_DRIVER, NULL);
/*
* Register a periodical handler.
*/
ssp->cycid = ddi_periodic_add(lombus_cyclic, ssp, LOMBUS_ONE_SEC,
DDI_IPL_1);
/*
* Final check before enabling h/w interrupts - did
* we successfully install the h/w interrupt handler?
*/
if (err != DDI_SUCCESS)
return (lombus_unattach(ssp, instance));
lombus_set_irq(ssp, B_TRUE);
/*
* All done, report success
*/
ddi_report_dev(dip);
return (DDI_SUCCESS);
}
static int
lombus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
struct lombus_state *ssp;
int instance;
switch (cmd) {
default:
return (DDI_FAILURE);
case DDI_DETACH:
break;
}
instance = ddi_get_instance(dip);
if ((ssp = lombus_getstate(dip, instance, "lombus_detach")) == NULL)
return (DDI_FAILURE); /* this "can't happen" */
(void) lombus_unattach(ssp, instance);
return (DDI_SUCCESS);
}
static int
lombus_reset(dev_info_t *dip, ddi_reset_cmd_t cmd)
{
struct lombus_state *ssp;
_NOTE(ARGUNUSED(cmd))
if ((ssp = lombus_getstate(dip, -1, "lombus_reset")) == NULL)
return (DDI_FAILURE);
lombus_hw_reset(ssp);
return (DDI_SUCCESS);
}
/*
* System interface structures
*/
static struct cb_ops lombus_cb_ops =
{
nodev, /* b/c open */
nodev, /* b/c close */
nodev, /* b strategy */
nodev, /* b print */
nodev, /* b dump */
nodev, /* c read */
nodev, /* c write */
nodev, /* c ioctl */
nodev, /* c devmap */
nodev, /* c mmap */
nodev, /* c segmap */
nochpoll, /* c poll */
ddi_prop_op, /* b/c prop_op */
NULL, /* c streamtab */
D_MP | D_NEW /* b/c flags */
};
static struct bus_ops lombus_bus_ops =
{
BUSO_REV, /* revision */
lombus_map, /* bus_map */
0, /* get_intrspec */
0, /* add_intrspec */
0, /* remove_intrspec */
i_ddi_map_fault, /* map_fault */
ddi_no_dma_map, /* dma_map */
ddi_no_dma_allochdl, /* allocate DMA handle */
ddi_no_dma_freehdl, /* free DMA handle */
ddi_no_dma_bindhdl, /* bind DMA handle */
ddi_no_dma_unbindhdl, /* unbind DMA handle */
ddi_no_dma_flush, /* flush DMA */
ddi_no_dma_win, /* move DMA window */
ddi_no_dma_mctl, /* generic DMA control */
lombus_ctlops, /* generic control */
ddi_bus_prop_op, /* prop_op */
ndi_busop_get_eventcookie, /* get_eventcookie */
ndi_busop_add_eventcall, /* add_eventcall */
ndi_busop_remove_eventcall, /* remove_eventcall */
ndi_post_event, /* post_event */
0, /* interrupt control */
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 */
};
static struct dev_ops lombus_dev_ops =
{
DEVO_REV,
0, /* refcount */
ddi_no_info, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
lombus_attach, /* attach */
lombus_detach, /* detach */
lombus_reset, /* reset */
&lombus_cb_ops, /* driver operations */
&lombus_bus_ops, /* bus operations */
NULL, /* power */
ddi_quiesce_not_supported, /* devo_quiesce */
};
static struct modldrv modldrv =
{
&mod_driverops,
"lombus driver",
&lombus_dev_ops
};
static struct modlinkage modlinkage =
{
MODREV_1,
{
&modldrv,
NULL
}
};
/*
* Dynamic loader interface code
*/
int
_init(void)
{
int err;
err = ddi_soft_state_init(&lombus_statep,
sizeof (struct lombus_state), 0);
if (err == DDI_SUCCESS)
if ((err = mod_install(&modlinkage)) != 0) {
ddi_soft_state_fini(&lombus_statep);
}
return (err);
}
int
_info(struct modinfo *mip)
{
return (mod_info(&modlinkage, mip));
}
int
_fini(void)
{
int err;
if ((err = mod_remove(&modlinkage)) == 0) {
ddi_soft_state_fini(&lombus_statep);
lombus_major = NOMAJOR;
}
return (err);
}