/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Authors: Joe Kloss; Ravi Pokala (rpokala@freebsd.org) * * Copyright (c) 2017-2018 Panasas * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* A detailed description of this device is present in imcsmb_pci.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imcsmb_reg.h" #include "imcsmb_var.h" /* Device methods */ static int imcsmb_attach(device_t dev); static int imcsmb_detach(device_t dev); static int imcsmb_probe(device_t dev); /* SMBus methods */ static int imcsmb_callback(device_t dev, int index, void *data); static int imcsmb_readb(device_t dev, u_char slave, char cmd, char *byte); static int imcsmb_readw(device_t dev, u_char slave, char cmd, short *word); static int imcsmb_writeb(device_t dev, u_char slave, char cmd, char byte); static int imcsmb_writew(device_t dev, u_char slave, char cmd, short word); /* All the read/write methods wrap around this. */ static int imcsmb_transfer(device_t dev, u_char slave, char cmd, void *data, int word_op, int write_op); /** * device_attach() method. Set up the softc, including getting the set of the * parent imcsmb_pci's registers that we will use. Create the smbus(4) device, * which any SMBus slave device drivers will connect to. * * @author rpokala * * @param[in,out] dev * Device being attached. */ static int imcsmb_attach(device_t dev) { struct imcsmb_softc *sc; int rc; /* Initialize private state */ sc = device_get_softc(dev); sc->dev = dev; sc->imcsmb_pci = device_get_parent(dev); sc->regs = device_get_ivars(dev); /* Create the smbus child */ sc->smbus = device_add_child(dev, "smbus", -1); if (sc->smbus == NULL) { /* Nothing has been allocated, so there's no cleanup. */ device_printf(dev, "Child smbus not added\n"); rc = ENXIO; goto out; } /* Attach the smbus child. */ if ((rc = bus_generic_attach(dev)) != 0) { device_printf(dev, "Failed to attach smbus: %d\n", rc); } out: return (rc); } /** * device_detach() method. attach() didn't do any allocations, so all that's * needed here is to free up any downstream drivers and children. * * @author Joe Kloss * * @param[in] dev * Device being detached. */ static int imcsmb_detach(device_t dev) { int rc; /* Detach any attached drivers */ rc = bus_generic_detach(dev); if (rc == 0) { /* Remove all children */ rc = device_delete_children(dev); } return (rc); } /** * device_probe() method. All the actual probing was done by the imcsmb_pci * parent, so just report success. * * @author Joe Kloss * * @param[in,out] dev * Device being probed. */ static int imcsmb_probe(device_t dev) { device_set_desc(dev, "iMC SMBus controller"); return (BUS_PROBE_DEFAULT); } /** * smbus_callback() method. Call the parent imcsmb_pci's request or release * function to quiesce / restart firmware tasks which might use the SMBus. * * @author rpokala * * @param[in] dev * Device being requested or released. * * @param[in] index * Either SMB_REQUEST_BUS or SMB_RELEASE_BUS. * * @param[in] data * Tell's the rest of the SMBus subsystem to allow or disallow waiting; * this driver only works with SMB_DONTWAIT. */ static int imcsmb_callback(device_t dev, int index, void *data) { struct imcsmb_softc *sc; int *how; int rc; sc = device_get_softc(dev); how = (int *) data; switch (index) { case SMB_REQUEST_BUS: { if (*how != SMB_DONTWAIT) { rc = EINVAL; goto out; } rc = imcsmb_pci_request_bus(sc->imcsmb_pci); break; } case SMB_RELEASE_BUS: imcsmb_pci_release_bus(sc->imcsmb_pci); rc = 0; break; default: rc = EINVAL; break; } out: return (rc); } /** * smbus_readb() method. Thin wrapper around imcsmb_transfer(). * * @author Joe Kloss * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[out] byte * The byte which was read. */ static int imcsmb_readb(device_t dev, u_char slave, char cmd, char *byte) { return (imcsmb_transfer(dev, slave, cmd, byte, FALSE, FALSE)); } /** * smbus_readw() method. Thin wrapper around imcsmb_transfer(). * * @author Joe Kloss * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[out] word * The word which was read. */ static int imcsmb_readw(device_t dev, u_char slave, char cmd, short *word) { return (imcsmb_transfer(dev, slave, cmd, word, TRUE, FALSE)); } /** * smbus_writeb() method. Thin wrapper around imcsmb_transfer(). * * @author Joe Kloss * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[in] byte * The byte to write. */ static int imcsmb_writeb(device_t dev, u_char slave, char cmd, char byte) { return (imcsmb_transfer(dev, slave, cmd, &byte, FALSE, TRUE)); } /** * smbus_writew() method. Thin wrapper around imcsmb_transfer(). * * @author Joe Kloss * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[in] word * The word to write. */ static int imcsmb_writew(device_t dev, u_char slave, char cmd, short word) { return (imcsmb_transfer(dev, slave, cmd, &word, TRUE, TRUE)); } /** * Manipulate the PCI control registers to read data from or write data to the * SMBus controller. * * @author Joe Kloss, rpokala * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[in,out] data * Pointer to either the value to be written, or where to place the value * which was read. * * @param[in] word_op * Bool: is this a word operation? * * @param[in] write_op * Bool: is this a write operation? */ static int imcsmb_transfer(device_t dev, u_char slave, char cmd, void *data, int word_op, int write_op) { struct imcsmb_softc *sc; int i; int rc; uint32_t cmd_val; uint32_t cntl_val; uint32_t orig_cntl_val; uint32_t stat_val; uint16_t *word; uint16_t lword; uint8_t *byte; uint8_t lbyte; sc = device_get_softc(dev); byte = data; word = data; lbyte = *byte; lword = *word; /* We modify the value of the control register; save the original, so * we can restore it later */ orig_cntl_val = pci_read_config(sc->imcsmb_pci, sc->regs->smb_cntl, 4); cntl_val = orig_cntl_val; /* * Set up the SMBCNTL register */ /* [31:28] Clear the existing value of the DTI bits, then set them to * the four high bits of the slave address. */ cntl_val &= ~IMCSMB_CNTL_DTI_MASK; cntl_val |= ((uint32_t) slave & 0xf0) << 24; /* [27:27] Set the CLK_OVERRIDE bit, to enable normal operation */ cntl_val |= IMCSMB_CNTL_CLK_OVERRIDE; /* [26:26] Clear the WRITE_DISABLE bit; the datasheet says this isn't * necessary, but empirically, it is. */ cntl_val &= ~IMCSMB_CNTL_WRITE_DISABLE_BIT; /* [9:9] Clear the POLL_EN bit, to stop the hardware TSOD polling. */ cntl_val &= ~IMCSMB_CNTL_POLL_EN; /* * Set up the SMBCMD register */ /* [31:31] Set the TRIGGER bit; when this gets written, the controller * will issue the command. */ cmd_val = IMCSMB_CMD_TRIGGER_BIT; /* [29:29] For word operations, set the WORD_ACCESS bit. */ if (word_op) { cmd_val |= IMCSMB_CMD_WORD_ACCESS; } /* [27:27] For write operations, set the WRITE bit. */ if (write_op) { cmd_val |= IMCSMB_CMD_WRITE_BIT; } /* [26:24] The three non-DTI, non-R/W bits of the slave address. */ cmd_val |= (uint32_t) ((slave & 0xe) << 23); /* [23:16] The command (offset in the case of an EEPROM, or register in * the case of TSOD or NVDIMM controller). */ cmd_val |= (uint32_t) ((uint8_t) cmd << 16); /* [15:0] The data to be written for a write operation. */ if (write_op) { if (word_op) { /* The datasheet says the controller uses different * endianness for word operations on I2C vs SMBus! * I2C: [15:8] = MSB; [7:0] = LSB * SMB: [15:8] = LSB; [7:0] = MSB * As a practical matter, this controller is very * specifically for use with DIMMs, the SPD (and * NVDIMM controllers) are only accessed as bytes, * the temperature sensor is only accessed as words, and * the temperature sensors are I2C. Thus, byte-swap the * word. */ lword = htobe16(lword); } else { /* For byte operations, the data goes in the LSB, and * the MSB is a don't care. */ lword = (uint16_t) (lbyte & 0xff); } cmd_val |= lword; } /* Write the updated value to the control register first, to disable * the hardware TSOD polling. */ pci_write_config(sc->imcsmb_pci, sc->regs->smb_cntl, cntl_val, 4); /* Poll on the BUSY bit in the status register until clear, or timeout. * We just cleared the auto-poll bit, so we need to make sure the device * is idle before issuing a command. We can safely timeout after 35 ms, * as this is the maximum time the SMBus spec allows for a transaction. */ for (i = 4; i != 0; i--) { stat_val = pci_read_config(sc->imcsmb_pci, sc->regs->smb_stat, 4); if ((stat_val & IMCSMB_STATUS_BUSY_BIT) == 0) { break; } pause("imcsmb", 10 * hz / 1000); } if (i == 0) { device_printf(sc->dev, "transfer: timeout waiting for device to settle\n"); } /* Now that polling has stopped, we can write the command register. This * starts the SMBus command. */ pci_write_config(sc->imcsmb_pci, sc->regs->smb_cmd, cmd_val, 4); /* Wait for WRITE_DATA_DONE/READ_DATA_VALID to be set, or timeout and * fail. We wait up to 35ms. */ for (i = 35000; i != 0; i -= 10) { DELAY(10); stat_val = pci_read_config(sc->imcsmb_pci, sc->regs->smb_stat, 4); /* For a write, the bits holding the data contain the data being * written. You'd think that would cause the READ_DATA_VALID bit * to be cleared, because the data bits no longer contain valid * data from the most recent read operation. While that would be * logical, that's not the case here: READ_DATA_VALID is only * cleared when starting a read operation, and WRITE_DATA_DONE * is only cleared when starting a write operation. */ if (write_op) { if ((stat_val & IMCSMB_STATUS_WRITE_DATA_DONE) != 0) { break; } } else { if ((stat_val & IMCSMB_STATUS_READ_DATA_VALID) != 0) { break; } } } if (i == 0) { rc = SMB_ETIMEOUT; device_printf(dev, "transfer timeout\n"); goto out; } /* It is generally the case that this bit indicates non-ACK, but it * could also indicate other bus errors. There's no way to tell the * difference. */ if ((stat_val & IMCSMB_STATUS_BUS_ERROR_BIT) != 0) { /* While it is not documented, empirically, SPD page-change * commands (writes with DTI = 0x60) always complete with the * error bit set. So, ignore it in those cases. */ if ((slave & 0xf0) != 0x60) { rc = SMB_ENOACK; goto out; } } /* For a read operation, copy the data out */ if (write_op == 0) { if (word_op) { /* The data is returned in bits [15:0]; as discussed * above, byte-swap. */ lword = (uint16_t) (stat_val & 0xffff); lword = htobe16(lword); *word = lword; } else { /* The data is returned in bits [7:0] */ lbyte = (uint8_t) (stat_val & 0xff); *byte = lbyte; } } /* A lack of an error is, de facto, success. */ rc = SMB_ENOERR; out: /* Restore the original value of the control register. */ pci_write_config(sc->imcsmb_pci, sc->regs->smb_cntl, orig_cntl_val, 4); return (rc); } /* Our device class */ static devclass_t imcsmb_devclass; /* Device methods */ static device_method_t imcsmb_methods[] = { /* Device interface */ DEVMETHOD(device_attach, imcsmb_attach), DEVMETHOD(device_detach, imcsmb_detach), DEVMETHOD(device_probe, imcsmb_probe), /* smbus methods */ DEVMETHOD(smbus_callback, imcsmb_callback), DEVMETHOD(smbus_readb, imcsmb_readb), DEVMETHOD(smbus_readw, imcsmb_readw), DEVMETHOD(smbus_writeb, imcsmb_writeb), DEVMETHOD(smbus_writew, imcsmb_writew), DEVMETHOD_END }; static driver_t imcsmb_driver = { .name = "imcsmb", .methods = imcsmb_methods, .size = sizeof(struct imcsmb_softc), }; DRIVER_MODULE(imcsmb, imcsmb_pci, imcsmb_driver, imcsmb_devclass, 0, 0); MODULE_DEPEND(imcsmb, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); MODULE_VERSION(imcsmb, 1); DRIVER_MODULE(smbus, imcsmb, smbus_driver, smbus_devclass, 0, 0); /* vi: set ts=8 sw=4 sts=8 noet: */