#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/pci.h>
#include <sys/sysmacros.h>
#include <sys/i2c/controller.h>
#include "pchsmbus.h"
#define PCHSMBUS_REGNO 1
typedef enum {
PCHSMBUS_INIT_PCI = 1 << 0,
PCHSMBUS_INIT_REGS = 1 << 1,
PCHSMBUS_INIT_INTR_ALLOC = 1 << 2,
PCHSMBUS_INIT_INTR_HDL = 1 << 3,
PCHSMBUS_INIT_SYNC = 1 << 4,
PCHSMBUS_INIT_CTRL = 1 << 5,
PCHSMBUS_INIT_INTR_EN = 1 << 6,
PCHSMBUS_INIT_I2C = 1 << 7,
PCHSMBUS_RUN_BUF_EN = 1 << 8,
PCHSMBUS_RUN_I2C_EN = 1 << 9
} pchsmbus_init_t;
typedef struct {
dev_info_t *ps_dip;
ddi_acc_handle_t ps_cfg;
pchsmbus_init_t ps_init;
pch_smbus_feat_t ps_feats;
caddr_t ps_base;
off_t ps_regsize;
ddi_acc_handle_t ps_regs;
uint32_t ps_init_hcfg;
uint8_t ps_init_hctl;
uint8_t ps_init_scmd;
int ps_nintrs;
ddi_intr_handle_t ps_intr_hdl;
uint_t ps_intr_pri;
kmutex_t ps_mutex;
kcondvar_t ps_cv;
i2c_ctrl_hdl_t *ps_hdl;
smbus_req_t *ps_req;
uint16_t ps_req_off;
uint8_t ps_req_hctl;
bool ps_req_done;
i2c_ctrl_error_t ps_kill_err;
} pchsmbus_t;
typedef struct {
uint16_t pcm_did;
pch_smbus_feat_t pcm_feat;
} pchsmbus_hw_map_t;
static const pchsmbus_hw_map_t pchsmbus_feats[] = {
{ PCH_SMBUS_ICH0_82801AA, 0 },
{ PCH_SMBUS_ICH0_82901AB, 0 },
{ PCH_SMBUS_ICH2_82801BA, PCH_SMBUS_FEAT_TARG },
{ PCH_SMBUS_ICH3_82801CA, PCH_SMBUS_FEAT_ALL_ICH3 },
{ PCH_SMBUS_ICH4_82801DB, PCH_SMBUS_FEAT_ALL_ICH4 },
{ PCH_SMBUS_ICH5_82801Ex, PCH_SMBUS_FEAT_ALL_ICH5 },
{ PCH_SMBUS_6300ESB, PCH_SMBUS_FEAT_ALL_ICH5 },
{ PCH_SMBUS_ICH6, PCH_SMBUS_FEAT_ALL_ICH5 },
{ PCH_SMBUS_631xESB, PCH_SMBUS_FEAT_ALL_ICH5 },
{ PCH_SMBUS_ICH7, PCH_SMBUS_FEAT_ALL_ICH5 },
{ PCH_SMBUS_ICH8, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_ICH9, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_ICH10_CORP, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_ICH10_USER, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_PCH5, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C600, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C600_SMB0, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C600_SMB1, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C600_SMB2, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_DH89xxCC, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_DH89xxCL, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_PCH6, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_PCH7, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_PCH8, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_PCH8_LP, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C610, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C610_MS0, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C610_MS1, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C610_MS2, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_PCH9, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_PCH9_LP, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_BAYTRAIL, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_100, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_DENVERTON, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C740, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_APOLLO, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_200, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_GEMINI, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C620, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_C620_SUPER, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_300, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_ICE_LAKE_D, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_495_PKG, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_400, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_400_PKG, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_ELKHART, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_500, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_500_PKG, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_JASPER, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_600, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_600_PKG, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_ALDER_N, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_700, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_800, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_METEOR_PS, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_PANTHER_H, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_PANTHER_P, PCH_SMBUS_FEAT_ALL_ICH8 },
{ PCH_SMBUS_900, PCH_SMBUS_FEAT_ALL_ICH8 }
};
static uint8_t
pchsmbus_read8(pchsmbus_t *pch, uint8_t regno)
{
ASSERT3U(regno, <, pch->ps_regsize);
return (ddi_get8(pch->ps_regs, (uint8_t *)(pch->ps_base + regno)));
}
static void
pchsmbus_write8(pchsmbus_t *pch, uint8_t regno, uint8_t val)
{
ASSERT3U(regno, <, pch->ps_regsize);
ddi_put8(pch->ps_regs, (uint8_t *)(pch->ps_base + regno), val);
}
static i2c_errno_t
pchsmbus_prop_info(void *arg, i2c_prop_t prop, i2c_prop_info_t *info)
{
switch (prop) {
case I2C_PROP_BUS_SPEED:
i2c_prop_info_set_def_u32(info, I2C_SPEED_STD);
i2c_prop_info_set_pos_bit32(info, I2C_SPEED_STD);
break;
case SMBUS_PROP_SUP_OPS:
case SMBUS_PROP_MAX_BLOCK:
break;
default:
return (I2C_PROP_E_UNSUP);
}
i2c_prop_info_set_perm(info, I2C_PROP_PERM_RO);
return (I2C_CORE_E_OK);
}
static i2c_errno_t
pchsmbus_prop_get(void *arg, i2c_prop_t prop, void *buf, size_t buflen)
{
uint32_t val;
switch (prop) {
case I2C_PROP_BUS_SPEED:
val = I2C_SPEED_STD;
break;
case SMBUS_PROP_SUP_OPS:
val = SMBUS_PROP_OP_QUICK_COMMAND | SMBUS_PROP_OP_SEND_BYTE |
SMBUS_PROP_OP_RECV_BYTE | SMBUS_PROP_OP_WRITE_BYTE |
SMBUS_PROP_OP_READ_BYTE | SMBUS_PROP_OP_WRITE_WORD |
SMBUS_PROP_OP_READ_WORD | SMBUS_PROP_OP_PROCESS_CALL |
SMBUS_PROP_OP_WRITE_BLOCK | SMBUS_PROP_OP_READ_BLOCK |
SMBUS_PROP_OP_BLOCK_PROCESS_CALL |
SMBUS_PROP_OP_I2C_WRITE_BLOCK |
SMBUS_PROP_OP_I2C_READ_BLOCK;
break;
case SMBUS_PROP_MAX_BLOCK:
val = SMBUS_V2_MAX_BLOCK;
break;
default:
return (I2C_PROP_E_UNSUP);
}
VERIFY3U(buflen, >=, sizeof (val));
bcopy(&val, buf, sizeof (val));
return (I2C_CORE_E_OK);
}
static bool
pchsmbus_bus_avail(pchsmbus_t *pch)
{
const uint32_t count = i2c_ctrl_timeout_count(pch->ps_hdl,
I2C_CTRL_TO_BUS_ACT);
const uint32_t wait = i2c_ctrl_timeout_delay_us(pch->ps_hdl,
I2C_CTRL_TO_BUS_ACT);
for (uint32_t i = 0; i < count; i++) {
uint8_t hsts = pchsmbus_read8(pch, PCH_R_BAR_HSTS);
if ((hsts & PCH_HSTS_BUSY) == 0) {
return (true);
}
delay(drv_usectohz(wait));
}
dev_err(pch->ps_dip, CE_WARN, "controller timed out waiting for "
"bus activity to cease");
return (false);
}
static void
pchsmbus_set_addr(pchsmbus_t *pch, const smbus_req_t *req, bool write)
{
uint8_t addr = 0;
uint8_t wbit = write ? PCH_R_TSA_RW_WRITE : PCH_R_TSA_RW_READ;
ASSERT3U(req->smbr_addr.ia_type, ==, I2C_ADDR_7BIT);
addr = PCH_R_TSA_SET_ADDR(addr, req->smbr_addr.ia_addr);
addr = PCH_R_TSA_SET_RW(addr, wbit);
pchsmbus_write8(pch, PCH_R_BAR_TSA, addr);
}
static pch_smbus_cmd_t
pchsmbus_req_to_cmd(const smbus_req_t *req)
{
switch (req->smbr_op) {
case SMBUS_OP_QUICK_COMMAND:
return (PCH_SMBUS_CMD_QUICK);
case SMBUS_OP_SEND_BYTE:
case SMBUS_OP_RECV_BYTE:
return (PCH_SMBUS_CMD_BYTE);
case SMBUS_OP_WRITE_BYTE:
case SMBUS_OP_READ_BYTE:
return (PCH_SMBUS_CMD_BYTE_DATA);
case SMBUS_OP_WRITE_WORD:
case SMBUS_OP_READ_WORD:
return (PCH_SMBUS_CMD_WORD_DATA);
case SMBUS_OP_WRITE_BLOCK:
case SMBUS_OP_READ_BLOCK:
case SMBUS_OP_I2C_WRITE_BLOCK:
return (PCH_SMBUS_CMD_BLOCK);
case SMBUS_OP_PROCESS_CALL:
return (PCH_SMBUS_CMD_PROC_CALL);
case SMBUS_OP_BLOCK_PROCESS_CALL:
return (PCH_SMBUS_CMD_BLOCK_PROC);
case SMBUS_OP_I2C_READ_BLOCK:
return (PCH_SMBUS_CMD_I2C_READ);
default:
panic("asked to translate unexpected request type: 0x%x",
req->smbr_op);
}
}
static void
pchsmbus_io_init_block(pchsmbus_t *pch, const smbus_req_t *req)
{
bool write, want_buf = false, want_i2c = false;
switch (req->smbr_op) {
case SMBUS_OP_WRITE_BLOCK:
case SMBUS_OP_BLOCK_PROCESS_CALL:
want_buf = true;
write = true;
break;
case SMBUS_OP_READ_BLOCK:
want_buf = true;
write = false;
break;
case SMBUS_OP_I2C_WRITE_BLOCK:
write = true;
want_buf = false;
want_i2c = true;
break;
case SMBUS_OP_I2C_READ_BLOCK:
write = PCH_R_HCFG_GET_SPDWD(pch->ps_init_hcfg) == 0;
break;
default:
panic("programmer error: not a block type: 0x%x\n",
req->smbr_op);
}
if ((pch->ps_feats & PCH_SMBUS_FEAT_32B_BUF) == 0)
want_buf = false;
VERIFY(!(want_i2c && want_buf));
if (want_i2c) {
uint32_t val = pci_config_get32(pch->ps_cfg, PCH_R_PCIE_HCFG);
val = PCH_R_HCFG_SET_I2CEN(val, 1);
pci_config_put32(pch->ps_cfg, PCH_R_PCIE_HCFG, val);
pch->ps_init |= PCHSMBUS_RUN_I2C_EN;
}
if (want_buf) {
uint8_t val = pchsmbus_read8(pch, PCH_R_BAR_AUXC);
val = PCH_R_AUXC_SET_E32B(val, 1);
pchsmbus_write8(pch, PCH_R_BAR_AUXC, val);
pch->ps_init |= PCHSMBUS_RUN_BUF_EN;
}
pchsmbus_set_addr(pch, req, write);
if (req->smbr_op == SMBUS_OP_I2C_READ_BLOCK) {
pchsmbus_write8(pch, PCH_R_BAR_HD1, req->smbr_cmd);
} else {
pchsmbus_write8(pch, PCH_R_BAR_HCMD, req->smbr_cmd);
}
if (req->smbr_op == SMBUS_OP_I2C_READ_BLOCK || SMBUS_OP_READ_BLOCK) {
return;
}
pchsmbus_write8(pch, PCH_R_BAR_HD0, req->smbr_wlen);
uint16_t wlen = req->smbr_wlen;
if (!want_buf) {
wlen = 1;
}
(void) pchsmbus_read8(pch, PCH_R_BAR_HCTL);
for (uint16_t i = 0; i < wlen; i++, pch->ps_req_off++) {
pchsmbus_write8(pch, PCH_R_BAR_HBD, req->smbr_wdata[i]);
}
}
static void
pchsmbus_io_error(pchsmbus_t *pch, pch_smbus_sts_t status)
{
i2c_ctrl_error_t err;
if ((status & PCH_HSTS_FAIL) != 0) {
ASSERT3U(pch->ps_kill_err, !=, I2C_CTRL_E_OK);
err = pch->ps_kill_err;
} else if ((status & PCH_HSTS_BUS_ERR)) {
err = I2C_CTRL_E_ARB_LOST;
} else {
err = I2C_CTRL_E_NACK;
}
i2c_ctrl_io_error(&pch->ps_req->smbr_error, I2C_CORE_E_CONTROLLER,
err);
pch->ps_req_done = true;
}
static void
pchsmbus_io_byte_done(pchsmbus_t *pch)
{
ASSERT(MUTEX_HELD(&pch->ps_mutex));
ASSERT3U(pch->ps_init & PCHSMBUS_RUN_BUF_EN, ==, 0);
ASSERT3P(pch->ps_req, !=, NULL);
if (pch->ps_req->smbr_op == SMBUS_OP_WRITE_BLOCK ||
pch->ps_req->smbr_op == SMBUS_OP_I2C_WRITE_BLOCK) {
if (pch->ps_req_off < pch->ps_req->smbr_wlen) {
pchsmbus_write8(pch, PCH_R_BAR_HBD,
pch->ps_req->smbr_wdata[pch->ps_req_off]);
pch->ps_req_off++;
}
return;
}
if (pch->ps_req->smbr_rlen == 0) {
ASSERT3U(pch->ps_req->smbr_op, ==, SMBUS_OP_READ_BLOCK);
uint8_t len = pchsmbus_read8(pch, PCH_R_BAR_HD0);
if (len == 0 || len > SMBUS_V2_MAX_BLOCK) {
pch->ps_kill_err = I2C_CTRL_E_BAD_SMBUS_RLEN;
uint8_t val = PCH_R_HCTL_SET_KILL(0, 1);
pchsmbus_write8(pch, PCH_R_BAR_HCTL, val);
return;
}
pch->ps_req->smbr_rlen = len;
return;
}
pch->ps_req->smbr_rdata[pch->ps_req_off] = pchsmbus_read8(pch,
PCH_R_BAR_HBD);
pch->ps_req_off++;
if (pch->ps_req_off + 1 == pch->ps_req->smbr_rlen) {
uint8_t hctl = PCH_R_HCTL_SET_LAST(pch->ps_req_hctl, 1);
pchsmbus_write8(pch, PCH_R_BAR_HCTL, hctl);
}
}
static void
pchsmbus_io_req_done(pchsmbus_t *pch)
{
uint8_t len;
pch->ps_req_done = true;
switch (pch->ps_req->smbr_op) {
case SMBUS_OP_QUICK_COMMAND:
case SMBUS_OP_SEND_BYTE:
case SMBUS_OP_WRITE_BYTE:
case SMBUS_OP_WRITE_WORD:
case SMBUS_OP_WRITE_BLOCK:
case SMBUS_OP_I2C_WRITE_BLOCK:
break;
case SMBUS_OP_RECV_BYTE:
case SMBUS_OP_READ_BYTE:
pch->ps_req->smbr_rdata[0] = pchsmbus_read8(pch, PCH_R_BAR_HD0);
break;
case SMBUS_OP_READ_WORD:
case SMBUS_OP_PROCESS_CALL:
pch->ps_req->smbr_rdata[0] = pchsmbus_read8(pch, PCH_R_BAR_HD0);
pch->ps_req->smbr_rdata[1] = pchsmbus_read8(pch, PCH_R_BAR_HD1);
break;
case SMBUS_OP_READ_BLOCK:
case SMBUS_OP_BLOCK_PROCESS_CALL:
case SMBUS_OP_I2C_READ_BLOCK:
if ((pch->ps_init & PCHSMBUS_RUN_BUF_EN) == 0) {
break;
}
len = pchsmbus_read8(pch, PCH_R_BAR_HD0);
if (len == 0 || len > SMBUS_V2_MAX_BLOCK) {
i2c_ctrl_io_error(&pch->ps_req->smbr_error,
I2C_CORE_E_CONTROLLER,
I2C_CTRL_E_BAD_SMBUS_RLEN);
return;
}
pch->ps_req->smbr_rlen = len;
(void) pchsmbus_read8(pch, PCH_R_BAR_HCTL);
for (uint16_t i = 0; i < pch->ps_req->smbr_rlen; i++) {
pch->ps_req->smbr_rdata[i] = pchsmbus_read8(pch,
PCH_R_BAR_HBD);
}
break;
case SMBUS_OP_HOST_NOTIFY:
case SMBUS_OP_WRITE_U32:
case SMBUS_OP_READ_U32:
case SMBUS_OP_WRITE_U64:
case SMBUS_OP_READ_U64:
default:
panic("programmer error: unsupported request type 0x%x should "
"not have been completed", pch->ps_req->smbr_op);
}
i2c_ctrl_io_success(&pch->ps_req->smbr_error);
}
static bool
pchsmbus_io(pchsmbus_t *pch, pch_smbus_sts_t status)
{
ASSERT(MUTEX_HELD(&pch->ps_mutex));
status &= PCH_HSTS_CLEAR_PRE;
if (status == 0) {
return (false);
}
if ((status & PCH_HSTS_ERRORS) != 0) {
pchsmbus_io_error(pch, status);
goto done;
}
if ((status & PCH_HSTS_BYTE_DONE) != 0) {
pchsmbus_io_byte_done(pch);
}
if ((status & PCH_HSTS_INTR) != 0) {
pchsmbus_io_req_done(pch);
}
done:
pchsmbus_write8(pch, PCH_R_BAR_HSTS, status);
return (true);
}
static uint_t
pchsmbus_intr(caddr_t arg1, caddr_t arg2)
{
pchsmbus_t *pch = (pchsmbus_t *)arg1;
pch_smbus_sts_t sts;
mutex_enter(&pch->ps_mutex);
sts = pchsmbus_read8(pch, PCH_R_BAR_HSTS);
if (!pchsmbus_io(pch, sts)) {
mutex_exit(&pch->ps_mutex);
return (DDI_INTR_UNCLAIMED);
}
if (pch->ps_req_done) {
cv_signal(&pch->ps_cv);
}
mutex_exit(&pch->ps_mutex);
return (DDI_INTR_CLAIMED);
}
static void
pchsmbus_wait(pchsmbus_t *pch, bool poll)
{
uint32_t to, spin;
VERIFY(MUTEX_HELD(&pch->ps_mutex));
VERIFY3P(pch->ps_req, !=, NULL);
to = i2c_ctrl_timeout_delay_us(pch->ps_hdl, I2C_CTRL_TO_IO);
spin = i2c_ctrl_timeout_delay_us(pch->ps_hdl, I2C_CTRL_TO_POLL_CTRL);
if (!poll) {
clock_t abs = ddi_get_lbolt() + drv_usectohz(to);
while (!pch->ps_req_done) {
clock_t ret = cv_timedwait(&pch->ps_cv, &pch->ps_mutex,
abs);
if (ret == -1) {
break;
}
}
} else {
hrtime_t abs = gethrtime() + USEC2NSEC(to);
while (!pch->ps_req_done && gethrtime() < abs) {
drv_usecwait(spin);
uint8_t status = pchsmbus_read8(pch, PCH_R_BAR_HSTS);
(void) pchsmbus_io(pch, status);
}
}
if (!pch->ps_req_done) {
uint8_t val = PCH_R_HCTL_SET_KILL(0, 1);
pchsmbus_write8(pch, PCH_R_BAR_HCTL, val);
i2c_ctrl_io_error(&pch->ps_req->smbr_error,
I2C_CORE_E_CONTROLLER, I2C_CTRL_E_REQ_TO);
pch->ps_req_done = true;
}
}
static void
pchsmbus_io_smbus(void *arg, uint32_t port, smbus_req_t *req)
{
bool poll;
pchsmbus_t *pch = arg;
ASSERT3U(port, ==, 0);
mutex_enter(&pch->ps_mutex);
if (!pchsmbus_bus_avail(pch)) {
mutex_exit(&pch->ps_mutex);
i2c_ctrl_io_error(&req->smbr_error, I2C_CORE_E_CONTROLLER,
I2C_CTRL_E_BUS_BUSY);
return;
}
ASSERT3P(pch->ps_req, ==, NULL);
pch->ps_req = req;
pch->ps_req_off = 0;
pch->ps_req_done = false;
poll = (req->smbr_flags & I2C_IO_REQ_F_POLL) != 0;
if (pch->ps_nintrs == 0)
poll = true;
switch (req->smbr_op) {
case SMBUS_OP_QUICK_COMMAND:
pchsmbus_set_addr(pch, req, (req->smbr_flags &
I2C_IO_REQ_F_QUICK_WRITE) != 0);
break;
case SMBUS_OP_SEND_BYTE:
pchsmbus_set_addr(pch, req, true);
pchsmbus_write8(pch, PCH_R_BAR_HCMD, req->smbr_wdata[0]);
break;
case SMBUS_OP_WRITE_BYTE:
pchsmbus_set_addr(pch, req, true);
pchsmbus_write8(pch, PCH_R_BAR_HCMD, req->smbr_cmd);
pchsmbus_write8(pch, PCH_R_BAR_HD0, req->smbr_wdata[0]);
break;
case SMBUS_OP_WRITE_WORD:
case SMBUS_OP_PROCESS_CALL:
pchsmbus_set_addr(pch, req, true);
pchsmbus_write8(pch, PCH_R_BAR_HCMD, req->smbr_cmd);
pchsmbus_write8(pch, PCH_R_BAR_HD0, req->smbr_wdata[0]);
pchsmbus_write8(pch, PCH_R_BAR_HD1, req->smbr_wdata[1]);
break;
case SMBUS_OP_RECV_BYTE:
pchsmbus_set_addr(pch, req, false);
break;
case SMBUS_OP_READ_BYTE:
case SMBUS_OP_READ_WORD:
pchsmbus_set_addr(pch, req, false);
pchsmbus_write8(pch, PCH_R_BAR_HCMD, req->smbr_cmd);
break;
case SMBUS_OP_WRITE_BLOCK:
case SMBUS_OP_BLOCK_PROCESS_CALL:
case SMBUS_OP_READ_BLOCK:
case SMBUS_OP_I2C_WRITE_BLOCK:
case SMBUS_OP_I2C_READ_BLOCK:
pchsmbus_io_init_block(pch, req);
break;
case SMBUS_OP_HOST_NOTIFY:
case SMBUS_OP_WRITE_U32:
case SMBUS_OP_READ_U32:
case SMBUS_OP_WRITE_U64:
case SMBUS_OP_READ_U64:
default:
dev_err(pch->ps_dip, CE_WARN, "!framework passed unsupported "
"SMBus command 0x%x", req->smbr_op);
i2c_ctrl_io_error(&req->smbr_error, I2C_CORE_E_CONTROLLER,
I2C_CTRL_E_UNSUP_CMD);
goto done;
}
pchsmbus_write8(pch, PCH_R_BAR_HSTS, PCH_HSTS_CLEAR_PRE);
pch_smbus_cmd_t cmd = pchsmbus_req_to_cmd(req);
uint8_t ctl = PCH_R_HCTL_SET_CMD(0, cmd);
ctl = PCH_R_HCTL_SET_START(ctl, 1);
ctl = PCH_R_HCTL_SET_INT_EN(ctl, !poll);
pchsmbus_write8(pch, PCH_R_BAR_HCTL, ctl);
pch->ps_req_hctl = ctl;
pchsmbus_wait(pch, poll);
done:
if ((pch->ps_init & PCHSMBUS_RUN_I2C_EN) != 0) {
uint32_t val = pci_config_get32(pch->ps_cfg, PCH_R_PCIE_HCFG);
val = PCH_R_HCFG_SET_I2CEN(val, 0);
pci_config_put32(pch->ps_cfg, PCH_R_PCIE_HCFG, val);
pch->ps_init &= ~PCHSMBUS_RUN_I2C_EN;
}
if ((pch->ps_init & PCHSMBUS_RUN_BUF_EN) != 0) {
uint8_t val = pchsmbus_read8(pch, PCH_R_BAR_AUXC);
val = PCH_R_AUXC_SET_E32B(val, 0);
pchsmbus_write8(pch, PCH_R_BAR_AUXC, val);
pch->ps_init &= ~PCHSMBUS_RUN_BUF_EN;
}
pch->ps_req = NULL;
pch->ps_req_off = 0;
pch->ps_req_hctl = 0;
pch->ps_req_done = false;
pch->ps_kill_err = I2C_CTRL_E_OK;
mutex_exit(&pch->ps_mutex);
}
static const i2c_ctrl_ops_t pchsmbus_ctrl_ops = {
.i2c_port_name_f = i2c_ctrl_port_name_portno,
.i2c_io_smbus_f = pchsmbus_io_smbus,
.i2c_prop_info_f = pchsmbus_prop_info,
.i2c_prop_get_f = pchsmbus_prop_get
};
static bool
pchsmbus_supported(pchsmbus_t *pch)
{
uint16_t id = pci_config_get16(pch->ps_cfg, PCI_CONF_VENID);
if (id != PCH_SMBUS_VID_INTEL) {
dev_err(pch->ps_dip, CE_WARN, "found unsupported non-Intel "
"vendor ID: 0x%x", id);
return (false);
}
id = pci_config_get16(pch->ps_cfg, PCI_CONF_DEVID);
for (size_t i = 0; i < ARRAY_SIZE(pchsmbus_feats); i++) {
if (id != pchsmbus_feats[i].pcm_did)
continue;
pch->ps_feats = pchsmbus_feats[i].pcm_feat;
return (true);
}
dev_err(pch->ps_dip, CE_WARN, "found unsupported device ID: 0x%x", id);
return (false);
}
static bool
pchsmbus_setup_regs(pchsmbus_t *pch)
{
int ret;
ddi_device_acc_attr_t attr;
bzero(&attr, sizeof (attr));
attr.devacc_attr_version = DDI_DEVICE_ATTR_V1;
attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
attr.devacc_attr_access = DDI_DEFAULT_ACC;
if (ddi_dev_regsize(pch->ps_dip, PCHSMBUS_REGNO, &pch->ps_regsize) !=
DDI_SUCCESS) {
dev_err(pch->ps_dip, CE_WARN, "failed to get regs[%u] size",
PCHSMBUS_REGNO);
return (false);
}
ret = ddi_regs_map_setup(pch->ps_dip, PCHSMBUS_REGNO, &pch->ps_base,
0, pch->ps_regsize, &attr, &pch->ps_regs);
if (ret != DDI_SUCCESS) {
dev_err(pch->ps_dip, CE_WARN, "failed to map regs[%u]: %u",
PCHSMBUS_REGNO, ret);
return (false);
}
pch->ps_init |= PCHSMBUS_INIT_REGS;
return (true);
}
static bool
pchsmbus_setup_intr(pchsmbus_t *pch)
{
int types, ret;
pch->ps_nintrs = 0;
pch->ps_intr_pri = 0;
if (PCH_R_HCFG_GET_SMI_EN(pch->ps_init_hcfg) != 0) {
dev_err(pch->ps_dip, CE_WARN, "!firmware has taken our "
"interrupt for itself via #SMI; limiting to polling");
return (true);
}
ret = ddi_intr_get_supported_types(pch->ps_dip, &types);
if (ret != DDI_SUCCESS) {
dev_err(pch->ps_dip, CE_WARN, "failed to get supported "
"interrupt types: 0x%x; limiting to polling", ret);
return (true);
}
if ((types & DDI_INTR_TYPE_FIXED) == 0) {
dev_err(pch->ps_dip, CE_WARN, "missing support for fixed "
"interrupts: found 0x%x; limiting to polling", types);
return (true);
}
ret = ddi_intr_alloc(pch->ps_dip, &pch->ps_intr_hdl,
DDI_INTR_TYPE_FIXED, 0, 1, &pch->ps_nintrs, DDI_INTR_ALLOC_STRICT);
if (ret != DDI_SUCCESS) {
dev_err(pch->ps_dip, CE_WARN, "failed to allocate "
"interrupts: 0x%x", ret);
return (false);
}
pch->ps_init |= PCHSMBUS_INIT_INTR_ALLOC;
ret = ddi_intr_add_handler(pch->ps_intr_hdl, pchsmbus_intr, pch, NULL);
if (ret != DDI_SUCCESS) {
dev_err(pch->ps_dip, CE_WARN, "failed to add interrupt "
"handler: 0x%x", ret);
return (false);
}
pch->ps_init |= PCHSMBUS_INIT_INTR_HDL;
ret = ddi_intr_get_pri(pch->ps_intr_hdl, &pch->ps_intr_pri);
if (ret != DDI_SUCCESS) {
dev_err(pch->ps_dip, CE_WARN, "failed to get interrupt "
"priority");
return (false);
}
return (true);
}
static void
pchsmbus_ctrl_init(pchsmbus_t *pch)
{
if ((pch->ps_feats & PCH_SMBUS_FEAT_HW_PEC) != 0) {
uint8_t val = pchsmbus_read8(pch, PCH_R_BAR_AUXC);
val = PCH_R_AUXC_SET_AAC(val, 0);
pchsmbus_write8(pch, PCH_R_BAR_AUXC, val);
}
if ((pch->ps_feats & PCH_SMBUS_FEAT_TARG_NOTIFY) != 0) {
pch->ps_init_scmd = pchsmbus_read8(pch, PCH_R_BAR_SCMD);
uint8_t val = PCH_R_SCMD_SET_SMB_D(pch->ps_init_scmd, 1);
val = PCH_R_SCMD_SET_HNI(val, 0);
pchsmbus_write8(pch, PCH_R_BAR_SCMD, 0);
}
pch->ps_init_hctl = pchsmbus_read8(pch, PCH_R_BAR_HCTL);
pch->ps_init_hctl = PCH_R_HCTL_SET_KILL(pch->ps_init_hctl, 0);
pchsmbus_write8(pch, PCH_R_BAR_HCTL, 0);
uint32_t val = pch->ps_init_hcfg;
val = PCH_R_HCFG_SET_EN(val, 1);
val = PCH_R_HCFG_SET_I2CEN(val, PCH_R_HCFG_I2CEN_SMBUS);
pci_config_put32(pch->ps_cfg, PCH_R_PCIE_HCFG, val);
pch->ps_init |= PCHSMBUS_INIT_CTRL;
}
static bool
pchsmbus_enable_intr(pchsmbus_t *pch)
{
int ret = ddi_intr_enable(pch->ps_intr_hdl);
if (ret != DDI_SUCCESS) {
dev_err(pch->ps_dip, CE_WARN, "failed to enable interrupt "
"handler: %d", ret);
return (false);
}
pch->ps_init |= PCHSMBUS_INIT_INTR_EN;
return (true);
}
static bool
pchsmbus_register(pchsmbus_t *pch)
{
i2c_ctrl_reg_error_t ret;
i2c_ctrl_register_t *reg;
ret = i2c_ctrl_register_alloc(I2C_CTRL_PROVIDER, ®);
if (ret != 0) {
dev_err(pch->ps_dip, CE_WARN, "failed to allocate i2c "
"controller registration structure: 0x%x", ret);
return (false);
}
reg->ic_type = I2C_CTRL_TYPE_SMBUS;
reg->ic_nports = 1;
reg->ic_dip = pch->ps_dip;
reg->ic_drv = pch;
reg->ic_ops = &pchsmbus_ctrl_ops;
ret = i2c_ctrl_register(reg, &pch->ps_hdl);
i2c_ctrl_register_free(reg);
if (ret != 0) {
dev_err(pch->ps_dip, CE_WARN, "failed to register with i2c "
"framework: 0x%x", ret);
return (false);
}
pch->ps_init |= PCHSMBUS_INIT_I2C;
return (true);
}
static void
pchsmbus_cleanup(pchsmbus_t *pch)
{
if ((pch->ps_init & PCHSMBUS_INIT_INTR_EN) != 0) {
int ret = ddi_intr_disable(pch->ps_intr_hdl);
if (ret != DDI_SUCCESS) {
dev_err(pch->ps_dip, CE_WARN, "failed to disable "
"interrupt handler: %d", ret);
}
pch->ps_init &= ~PCHSMBUS_INIT_INTR_EN;
}
if ((pch->ps_init & PCHSMBUS_INIT_CTRL) != 0) {
if ((pch->ps_feats & PCH_SMBUS_FEAT_TARG_NOTIFY) != 0) {
pchsmbus_write8(pch, PCH_R_BAR_SCMD, pch->ps_init_scmd);
}
pchsmbus_write8(pch, PCH_R_BAR_HCTL, pch->ps_init_hctl);
pci_config_put32(pch->ps_cfg, PCH_R_PCIE_HCFG,
pch->ps_init_hcfg);
pch->ps_init &= ~PCHSMBUS_INIT_CTRL;
}
if ((pch->ps_init & PCHSMBUS_INIT_SYNC) != 0) {
cv_destroy(&pch->ps_cv);
mutex_destroy(&pch->ps_mutex);
pch->ps_init &= ~PCHSMBUS_INIT_SYNC;
}
if ((pch->ps_init & PCHSMBUS_INIT_INTR_HDL) != 0) {
int ret = ddi_intr_remove_handler(pch->ps_intr_hdl);
if (ret != 0) {
dev_err(pch->ps_dip, CE_WARN, "failed to remove "
"interrupt handler: 0x%x", ret);
}
pch->ps_init &= ~PCHSMBUS_INIT_INTR_HDL;
}
if ((pch->ps_init & PCHSMBUS_INIT_INTR_ALLOC) != 0) {
int ret = ddi_intr_free(pch->ps_intr_hdl);
if (ret != DDI_SUCCESS) {
dev_err(pch->ps_dip, CE_WARN, "failed to free "
"device interrupt: 0x%x", ret);
}
pch->ps_init &= ~PCHSMBUS_INIT_INTR_ALLOC;
}
if ((pch->ps_init & PCHSMBUS_INIT_REGS) != 0) {
ddi_regs_map_free(&pch->ps_regs);
pch->ps_base = NULL;
pch->ps_regsize = 0;
pch->ps_init &= ~PCHSMBUS_INIT_REGS;
}
if ((pch->ps_init & PCHSMBUS_INIT_PCI) != 0) {
pci_config_teardown(&pch->ps_cfg);
pch->ps_cfg = NULL;
pch->ps_init &= ~PCHSMBUS_INIT_PCI;
}
ASSERT0(pch->ps_init);
ddi_set_driver_private(pch->ps_dip, NULL);
kmem_free(pch, sizeof (pchsmbus_t));
}
int
pchsmbus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
pchsmbus_t *pch;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
default:
return (DDI_FAILURE);
}
pch = kmem_zalloc(sizeof (pchsmbus_t), KM_SLEEP);
pch->ps_dip = dip;
ddi_set_driver_private(dip, pch);
if (pci_config_setup(dip, &pch->ps_cfg) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "failed to set up config space");
goto cleanup;
}
pch->ps_init |= PCHSMBUS_INIT_PCI;
if (!pchsmbus_supported(pch))
goto cleanup;
if (!pchsmbus_setup_regs(pch))
goto cleanup;
pch->ps_init_hcfg = pci_config_get32(pch->ps_cfg, PCH_R_PCIE_HCFG);
if (!pchsmbus_setup_intr(pch))
goto cleanup;
mutex_init(&pch->ps_mutex, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(pch->ps_intr_pri));
cv_init(&pch->ps_cv, NULL, CV_DRIVER, NULL);
pch->ps_init |= PCHSMBUS_INIT_SYNC;
pchsmbus_ctrl_init(pch);
if (!pchsmbus_enable_intr(pch))
goto cleanup;
if (!pchsmbus_register(pch))
goto cleanup;
return (DDI_SUCCESS);
cleanup:
pchsmbus_cleanup(pch);
return (DDI_FAILURE);
}
int
pchsmbus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
pchsmbus_t *pch;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
default:
return (DDI_FAILURE);
}
pch = ddi_get_driver_private(dip);
if (pch == NULL) {
dev_err(dip, CE_WARN, "asked to detach, but missing private "
"data");
return (DDI_FAILURE);
}
VERIFY3P(pch->ps_dip, ==, dip);
i2c_ctrl_reg_error_t ret = i2c_ctrl_unregister(pch->ps_hdl);
if (ret != 0) {
dev_err(dip, CE_WARN, "failed to unregister from i2c "
"framework 0x%x", ret);
return (DDI_FAILURE);
}
pch->ps_init &= ~PCHSMBUS_INIT_I2C;
pchsmbus_cleanup(pch);
return (DDI_SUCCESS);
}
static struct dev_ops pchsmbus_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = pchsmbus_attach,
.devo_detach = pchsmbus_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_supported,
};
static struct modldrv pchsmbus_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "Intel ICH/PCH SMBus Controller",
.drv_dev_ops = &pchsmbus_dev_ops
};
static struct modlinkage pchsmbus_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &pchsmbus_modldrv, NULL }
};
int
_init(void)
{
int ret;
i2c_ctrl_mod_init(&pchsmbus_dev_ops);
if ((ret = mod_install(&pchsmbus_modlinkage)) != 0) {
i2c_ctrl_mod_fini(&pchsmbus_dev_ops);
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&pchsmbus_modlinkage, modinfop));
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&pchsmbus_modlinkage)) == 0) {
i2c_ctrl_mod_fini(&pchsmbus_dev_ops);
}
return (ret);
}