Michał Żygowski has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/30758
Change subject: [RFC] src/soc/intel/fsp_baytrail/smbus: extended addressing support for BayTrail ......................................................................
[RFC] src/soc/intel/fsp_baytrail/smbus: extended addressing support for BayTrail
Found it on one of private repositories I have access to. Do not have any use case for it for now.
Signed-off-by: Michał Żygowski michal.zygowski@3mdeb.com Change-Id: I39178776ef8ccae785ec5f6edb2824929cd103e9 --- A src/soc/intel/fsp_baytrail/include/soc/smbus.h A src/soc/intel/fsp_baytrail/smbus.c A src/soc/intel/fsp_baytrail/smbus_access.c 3 files changed, 956 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/58/30758/1
diff --git a/src/soc/intel/fsp_baytrail/include/soc/smbus.h b/src/soc/intel/fsp_baytrail/include/soc/smbus.h new file mode 100644 index 0000000..c97423e --- /dev/null +++ b/src/soc/intel/fsp_baytrail/include/soc/smbus.h @@ -0,0 +1,145 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2014 Sage Electronic Engineering, LLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc. + */ + + +#define FULL_RW 0x48 +#define QUICK_RW 0x44 +#define IGNORED_BYTE 0x00 +#define READ_BIT 0x01 +#define ADDRESS_16_BIT 0x10000000ul + +#define SMBUS_IO_ROUTINES 0x01 +#define SMB_IO_BASE 0x20 +#define I2C_MEM_BASE 0x10 +#define HOSTCFG 0x40 + +/* HOSTCFG bits */ +#define HST_EN (1 << 0) +#define SMB_SMI_EN (1 << 1) +#define I2C_EN (1 << 2) +#define SSRT (1 << 3) +#define SPD_WD (1 << 4) + +/* SMBus I/O bits. */ +#define SMBHSTSTAT 0x0 +#define SMBHSTCTL 0x2 +#define SMBHSTCMD 0x3 +#define SMBXMITADD 0x4 +#define SMBHSTDAT0 0x5 +#define SMBHSTDAT1 0x6 +#define SMBBLKDAT 0x7 +#define SMBTRNSADD 0x9 +#define SMBSLVDATA 0xa +#define SMLINK_PIN_CTL 0xe +#define SMBUS_PIN_CTL 0xf + +#define SMBUS_TIMEOUT (10 * 1000 * 100) + +#define HSTSTS_HOST_BUSY (1 << 0) +#define HSTSTS_INTR (1 << 1) +#define HSTSTS_DEV_ERR (1 << 2) +#define HSTSTS_BUS_ERR (1 << 3) +#define HSTSTS_FAILED (1 << 4) +#define HSTSTS_SMBALERT_STS (1 << 5) +#define HSTSTS_INUSE_STS (1 << 6) +#define HSTSTS_BYTE_DONE_STS (1 << 7) + +/* SIO I2C memory mapped registers */ +#define IC_CON 0x00 +# define IC_CON_MASTER_MODE (1 << 0) +# define IC_CON_10BITADDR_MASTER (1 << 4) +# define IC_CON_7BITADDR_MASTER 0 +# define IC_CON_RESTART_EN (1 << 5) +# define IC_CON_SLAVE_DISABLE (1 << 6) +#define IC_TAR 0x04 +# define IC_TAR_10BITADDR_MASTER (1 << 12) +# define IC_TAR_7BITADDR_MASTER 0 +#define IC_HS_MADDR 0x0C +#define IC_DATA_CMD 0x10 +#define IC_SS_SCL_HCNT 0x14 +#define IC_SS_SCL_LCNT 0x18 +# define IC_DATA_CMD_WRITE 0 +# define IC_DATA_CMD_READ (1 << 8) +# define IC_DATA_CMD_STOP (1 << 9) +#define IC_RAW_INTR_STAT 0x34 +# define IC_STAT_RX_UNDER (1 << 0) +# define IC_STAT_RX_OVER (1 << 1) +# define IC_STAT_RX_FULL (1 << 2) +# define IC_STAT_TX_OVER (1 << 3) +# define IC_STAT_TX_EMPTY (1 << 4) +# define IC_STAT_RD_REQ (1 << 5) +# define IC_STAT_TX_ABRT (1 << 6) +# define IC_STAT_RX_DONE (1 << 7) +# define IC_STAT_ACTIVITY (1 << 8) +# define IC_STAT_STOP_DET (1 << 9) +# define IC_STAT_START_DET (1 << 10) +# define IC_STAT_GEN_CALL (1 << 11) +#define IC_CLR_INTR 0x40 +# define IC_CLR_INTR_CLR_INTR (1 << 0) +#define IC_STATUS 0x70 +# define IC_STATUS_ACTIVITY (1 << 0) +# define IC_STATUS_TFNF (1 << 1) +# define IC_STATUS_TFE (1 << 2) +# define IC_STATUS_RFNE (1 << 3) +# define IC_STATUS_RFF (1 << 4) +# define IC_STATUS_MST_ACTIVITY (1 << 5) +# define IC_STATUS_SLV_ACTIVITY (1 << 6) +#define IC_ENABLE 0x6C +# define IC_ENABLE_ENABLE (1 << 0) +# define IC_ENABLE_DISABLE 0 +# define IC_ENABLE_ABORT (1 << 1) +#define IC_RXFLR 0x78 +#define IC_TX_ABRT_SOURCE 0x80 +# define ABRT_7B_ADDR_NOACK (1 << 0) +# define ABRT_10ADDR1_NOACK (1 << 1) +# define ABRT_10ADDR2_NOACK (1 << 2) +# define ABRT_TXDATA_NOACK (1 << 3) +# define ABRT_GCALL_NOAC (1 << 4) +# define ABRT_GCALL_READ (1 << 5) +# define ABRT_HS_ACKDET (1 << 6) +# define ABRT_SBYTE_ACKDET (1 << 7) +# define ABRT_HS_NORSTRT (1 << 8) +# define ABRT_SBYTE_NORSTRT (1 << 9) +# define ABRT_10B_RD_NORSTRT (1 << 10) +# define ABRT_MASTER_DIS (1 << 11) +# define ARB_LOST (1 << 12) +# define ABRT_SLVFLUSH_TXFIFO (1 << 13) +# define ABRT_SLV_ARBLOST (1 << 14) +# define ABRT_SLVRD_INTX (1 << 15) +# define ABRT_USER_ABRT (1 << 16) + +#define SET_IOSF_REG(name_) \ + case PCI_DEVFN(name_ ## _DEV, name_ ## _FUNC): \ + *iosf_reg = LPSS_ ## name_ ## _CTL; \ + *nvs_index = LPSS_NVS_ ## name_ + +#define CASE_I2C(name_) \ + case PCI_DEVFN(name_ ## _DEV, name_ ## _FUNC) + +#ifdef __PRE_RAM__ +void enable_smbus(device_t dev, u16 smbus_io_base); +#endif + +s16 baytrail_smbus_write_byte(u16 smbus_base, u8 device, u8 address, u8 data); +s16 baytrail_smbus_read_byte(u32 smbus_base, u8 device, u8 address); +s16 baytrail_smbus_quick_write(u32 smbus_base, u8 device, u8 data); +s16 baytrail_smbus_quick_read(u32 smbus_base, u8 device); +s16 baytrail_smbus_extended_write_byte(u32 smbus_base, u8 device, u16 address, u8 data); +s16 baytrail_smbus_extended_read_byte(u32 smbus_base, u8 device, u16 address); + diff --git a/src/soc/intel/fsp_baytrail/smbus.c b/src/soc/intel/fsp_baytrail/smbus.c new file mode 100644 index 0000000..83da6f5 --- /dev/null +++ b/src/soc/intel/fsp_baytrail/smbus.c @@ -0,0 +1,319 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2013 Google Inc. + * Copyright (C) 2014 Sage Electronic Engineering, LLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc. + */ + +#include <console/console.h> +#include <device/pci_def.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <device/pci_ops.h> +#include <device/smbus.h> +#include <device/smbus_def.h> +#include <soc/gpio.h> +#include <soc/pci_devs.h> +#include <soc/iomap.h> +#include <soc/iosf.h> +#include <soc/smbus.h> +#include <soc/iosf.h> +#include <soc/nvs.h> +#include <reg_script.h> + +static void dev_enable_snoop_and_pm(struct device * dev, int iosf_reg) +{ + struct reg_script ops[] = { + REG_SCRIPT_SET_DEV(dev), + REG_IOSF_RMW(IOSF_PORT_LPSS, iosf_reg, + ~(LPSS_CTL_SNOOP | LPSS_CTL_NOSNOOP), + LPSS_CTL_SNOOP | LPSS_CTL_PM_CAP_PRSNT), + REG_SCRIPT_END, }; + + reg_script_run(ops); +} + +static void dev_ctl_reg(struct device * dev, int *iosf_reg, int *nvs_index) +{ + *iosf_reg = -1; + *nvs_index = -1; + + switch (dev->path.pci.devfn) { + SET_IOSF_REG(I2C1); + break; + SET_IOSF_REG(I2C2); + break; + SET_IOSF_REG(I2C3); + break; + SET_IOSF_REG(I2C4); + break; + SET_IOSF_REG(I2C5); + break; + SET_IOSF_REG(I2C6); + break; + SET_IOSF_REG(I2C7); + break; + } +} + +static void i2c_set_pins(struct device *dev) +{ +#define PIN_SET_FOR_I2C 0x2003C881 + switch (dev->path.pci.devfn) { + CASE_I2C(I2C1) : + write32((volatile void *)(IO_BASE_ADDRESS + 0x0210), + PIN_SET_FOR_I2C); + write32((volatile void *)(IO_BASE_ADDRESS + 0x0200), + PIN_SET_FOR_I2C); + break; + CASE_I2C(I2C2) : + write32((volatile void *)(IO_BASE_ADDRESS + 0x01F0), + PIN_SET_FOR_I2C); + write32((volatile void *)(IO_BASE_ADDRESS + 0x01E0), + PIN_SET_FOR_I2C); + break; + CASE_I2C(I2C3) : + write32((volatile void *)(IO_BASE_ADDRESS + 0x01D0), + PIN_SET_FOR_I2C); + write32((volatile void *)(IO_BASE_ADDRESS + 0x01B0), + PIN_SET_FOR_I2C); + break; + CASE_I2C(I2C4) : + write32((volatile void *)(IO_BASE_ADDRESS + 0x0190), + PIN_SET_FOR_I2C); + write32((volatile void *)(IO_BASE_ADDRESS + 0x01C0), + PIN_SET_FOR_I2C); + break; + CASE_I2C(I2C5) : + write32((volatile void *)(IO_BASE_ADDRESS + 0x01A0), + PIN_SET_FOR_I2C); + write32((volatile void *)(IO_BASE_ADDRESS + 0x0170), + PIN_SET_FOR_I2C); + break; + CASE_I2C(I2C6) : + write32((volatile void *)(IO_BASE_ADDRESS + 0x0150), + PIN_SET_FOR_I2C); + write32((volatile void *)(IO_BASE_ADDRESS + 0x0140), + PIN_SET_FOR_I2C); + break; + CASE_I2C(I2C7) : + write32((volatile void *)(IO_BASE_ADDRESS + 0x0180), + PIN_SET_FOR_I2C); + write32((volatile void *)(IO_BASE_ADDRESS + 0x0160), + PIN_SET_FOR_I2C); + break; + } +} + +static void i2c_disable_resets(struct device * dev) +{ + /* Release the I2C devices from reset. */ + struct reg_script ops[] = { + REG_SCRIPT_SET_DEV(dev), + REG_RES_WRITE32(PCI_BASE_ADDRESS_0, 0x804, 0x3), + REG_SCRIPT_END, }; + + reg_script_run(ops); +} + +static void baytrail_smbus_init(struct device *dev) +{ + int iosf_reg, nvs_index; + + printk(BIOS_DEBUG, "Initializing I2c/SMBus at %s\n", dev_path(dev)); + + switch (dev->path.pci.devfn) { + CASE_I2C(I2C1) : + CASE_I2C(I2C2) : + CASE_I2C(I2C3) : + CASE_I2C(I2C4) : + CASE_I2C(I2C5) : + CASE_I2C(I2C6) : + CASE_I2C(I2C7) : + dev_ctl_reg(dev, &iosf_reg, &nvs_index); + + if (iosf_reg < 0) { + int slot = PCI_SLOT(dev->path.pci.devfn); + int func = PCI_FUNC(dev->path.pci.devfn); + printk(BIOS_DEBUG, + "Could not find iosf_reg for %02x.%01x\n", slot, + func); + return; + } + + dev_enable_snoop_and_pm(dev, iosf_reg); + i2c_disable_resets(dev); + i2c_set_pins(dev); + break; + default: + return; + } +} + +/** + * \brief - get required information about the PCI bus device that an SMBus/I2c + * device is attached to. + * + * @param dev - pointer to SMBus/I2c device - not the PCI SMBus device, + * but the device attached to the SMBus + * @param device - returns the 7/10 bit SMBus chip address + * @param pbus - returns the PCI SMBus/I2c device that the chip is attached to + * @param res - IO or memory base address of the SMBus or I2c PCI device + */ +static void get_smbus_device_info(struct device * dev, u16 *device, + struct bus **pbus, struct resource **res) +{ + *device = dev->path.i2c.device; + *pbus = get_pbus_smbus(dev); + if (dev->device == SMBUS_DEVID) { + *res = find_resource((*pbus)->dev, SMB_IO_BASE); + (*res)->base |= SMBUS_IO_ROUTINES; + } else + *res = find_resource((*pbus)->dev, I2C_MEM_BASE); +} + +/** + * \brief read a byte from an SMBus device that requires a 2 byte address + * @param dev - pointer to SMBus/I2c device + * @param address - the 16 bit address of the byte to read from the device + * @return byte - value read from SMBUS or value < 0 for error + */ +static int lsmbus_extended_read_byte(struct device * dev, u16 address) +{ + u16 device; + struct resource *res; + struct bus *pbus; + u16 retval; + + get_smbus_device_info(dev, &device, &pbus, &res); + retval = baytrail_smbus_extended_read_byte(res->base, device, address); + + return retval; +} + +/** + * \brief write a byte to an SMBus device that requires a 2 byte address + * @param dev - pointer to SMBus/I2c device + * @param address - the 16 bit address of the byte to write to the device + * @return byte - 0 for success or value < 0 for error + */ +static int lsmbus_extended_write_byte(struct device * dev, u16 address, u8 val) +{ + u16 device; + struct resource *res; + struct bus *pbus; + u16 retval; + + get_smbus_device_info(dev, &device, &pbus, &res); + + retval = baytrail_smbus_extended_write_byte(res->base, device, address, + val); + + return retval; +} + +static int lsmbus_read_byte(struct device * dev, u8 address) +{ + u16 device; + struct resource *res; + struct bus *pbus; + + get_smbus_device_info(dev, &device, &pbus, &res); + + return baytrail_smbus_read_byte(res->base, device, address); +} + +static int lsmbus_quick_read_byte(struct device * dev) +{ + u16 device; + struct resource *res; + struct bus *pbus; + + get_smbus_device_info(dev, &device, &pbus, &res); + + return baytrail_smbus_quick_read(res->base, device); +} + +static int lsmbus_write_byte(struct device * dev, u8 address, u8 val) +{ + u16 device; + struct resource *res; + struct bus *pbus; + + get_smbus_device_info(dev, &device, &pbus, &res); + + return baytrail_smbus_write_byte(res->base, device, address, val); +} + +static int lsmbus_quick_write_byte(struct device * dev, u8 value) +{ + u16 device; + struct resource *res; + struct bus *pbus; + + get_smbus_device_info(dev, &device, &pbus, &res); + + return baytrail_smbus_quick_write(res->base, device, value); +} + +const struct smbus_bus_operations baytrail_smbus_bus_ops = { + .read_byte = lsmbus_read_byte, + .write_byte = lsmbus_write_byte, + .recv_byte = lsmbus_quick_read_byte, + .send_byte = lsmbus_quick_write_byte, + .extended_read_byte = lsmbus_extended_read_byte, + .extended_write_byte = lsmbus_extended_write_byte, + +}; + +static void smbus_set_subsystem(struct device * dev, unsigned vendor, unsigned device) +{ + if (!vendor || !device) { + pci_write_config32(dev, PCI_SUBSYSTEM_VENDOR_ID, + pci_read_config32(dev, PCI_VENDOR_ID)); + } else { + pci_write_config32(dev, PCI_SUBSYSTEM_VENDOR_ID, + ((device & 0xffff) << 16) | (vendor & 0xffff)); + } +} + +static struct pci_operations baytrail_smbus_pci_ops = { + .set_subsystem = smbus_set_subsystem, }; + +struct device_operations baytrail_smbus_ops = { + .read_resources = pci_dev_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_dev_enable_resources, + .init = baytrail_smbus_init, + .scan_bus = 0, + .ops_smbus_bus = &baytrail_smbus_bus_ops, + .ops_pci = &baytrail_smbus_pci_ops, }; + +static const unsigned short pci_device_ids[] = { + I2C1_DEVID, + I2C2_DEVID, + I2C3_DEVID, + I2C4_DEVID, + I2C5_DEVID, + I2C6_DEVID, + I2C7_DEVID, + SMBUS_DEVID, + 0, +}; + +static const struct pci_driver baytrail_smbus __pci_driver = { .ops = + &baytrail_smbus_ops, .vendor = PCI_VENDOR_ID_INTEL, .devices = + pci_device_ids, }; diff --git a/src/soc/intel/fsp_baytrail/smbus_access.c b/src/soc/intel/fsp_baytrail/smbus_access.c new file mode 100644 index 0000000..0db2557 --- /dev/null +++ b/src/soc/intel/fsp_baytrail/smbus_access.c @@ -0,0 +1,492 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2008-2009 coresystems GmbH + * Copyright (C) 2012-2014 Sage Electronic Engineering, LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc. + */ + +#include <arch/io.h> +#include <console/console.h> +#include <device/pci_ids.h> +#include <device/pci_def.h> +#include <device/smbus_def.h> +#include <soc/smbus.h> +#include <delay.h> + +/** \brief block until the SMBus is no longer busy, or it times out + * + * \param smbus_base IO base address of the SMBus + */ +static int smbus_wait_until_ready(u16 smbus_base) +{ + u32 loops = SMBUS_TIMEOUT; + u8 byte; + + do { + udelay(1); + if (--loops == 0) + break; + byte = inb(smbus_base + SMBHSTSTAT); + } while (byte & HSTSTS_HOST_BUSY); + return loops ? 0 : -1; +} + +/** \brief block until the SMBus is no longer busy, in use, or it times out + * + * \param smbus_base IO base address of the SMBus + */ +static int smbus_wait_until_done(u16 smbus_base) +{ + u32 loops = SMBUS_TIMEOUT; + u8 byte; + + do { + udelay(1); + if (--loops == 0) + break; + byte = inb(smbus_base + SMBHSTSTAT); + } while ((byte & HSTSTS_HOST_BUSY) + || (byte & ~(HSTSTS_INUSE_STS | HSTSTS_HOST_BUSY)) == 0); + return loops ? 0 : -1; +} + +/** \brief clear the smbus in-use status bit + * + * @param smbus_base IO base address of the SMBus + */ +static void smbus_clear_inuse(u16 smbus_base) +{ + /* Clear INUSE_STS */ + outb((u16)smbus_base + SMBHSTSTAT, HSTSTS_INUSE_STS); +} + +#ifdef __PRE_RAM__ +/** \brief Sets the SMBus BAR, and configures it to run + * + */ +void enable_smbus(device_t dev, u16 smbus_base) +{ + /* TODO: Make this work for all SMBUS and I2c devices on baytrail */ + /* Set SMBus I/O base. */ + pci_write_config32(dev, SMB_IO_BASE, smbus_base | + PCI_BASE_ADDRESS_SPACE_IO); + + /* Set SMBus enable. */ + pci_write_config8(dev, HOSTCFG, HST_EN); + + /* Set SMBus I/O space enable. */ + pci_write_config16(dev, PCI_COMMAND, PCI_COMMAND_IO); + + /* Disable interrupt generation. */ + outb(0, smbus_base + SMBHSTCTL); + + /* Clear any lingering errors, so transactions can run. */ + outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT); + print_debug("SMBus controller enabled.\n"); + smbus_clear_inuse(smbus_base); +} +#endif + +/* + * The functions for setting the speed, along with the timings used were + * pulled from the linux source tree. + * Linux/drivers/i2c/busses/i2c-designware-core.c + * This is the module that is used for the Bay Trail I2c hardware + * + * The two functions are i2c_dw_scl_hcnt() and i2c_dw_scl_lcnt() + * The timing code is i2c_rw_byte() calling the two functions. + * + */ +static u32 i2c_dw_scl_hcnt(u32 ic_clk, u32 tSYMBOL, u32 tf, int cond, + int offset) +{ + /* + * DesignWare I2C core doesn't seem to have solid strategy to meet + * the tHD;STA timing spec. Configuring _HCNT based on tHIGH spec + * will result in violation of the tHD;STA spec. + */ + if (cond) + /* + * Conditional expression: + * + * IC_[FS]S_SCL_HCNT + (1+4+3) >= IC_CLK * tHIGH + * + * This is based on the DW manuals, and represents an ideal + * configuration. The resulting I2C bus speed will be + * faster than any of the others. + * + * If your hardware is free from tHD;STA issue, try this one. + */ + return (ic_clk * tSYMBOL + 5000) / 10000 - 8 + offset; + else + /* + * Conditional expression: + * + * IC_[FS]S_SCL_HCNT + 3 >= IC_CLK * (tHD;STA + tf) + * + * This is just experimental rule; the tHD;STA period turned + * out to be proportional to (_HCNT + 3). With this setting, + * we could meet both tHIGH and tHD;STA timing specs. + * + * If unsure, you'd better to take this alternative. + * + * The reason why we need to take into account "tf" here, + * is the same as described in i2c_dw_scl_lcnt(). + */ + return (ic_clk * (tSYMBOL + tf) + 5000) / 10000 - 3 + offset; +} + +static u32 i2c_dw_scl_lcnt(u32 ic_clk, u32 tLOW, u32 tf, int offset) +{ + /* + * Conditional expression: + * + * IC_[FS]S_SCL_LCNT + 1 >= IC_CLK * (tLOW + tf) + * + * DW I2C core starts counting the SCL CNTs for the LOW period + * of the SCL clock (tLOW) as soon as it pulls the SCL line. + * In order to meet the tLOW timing spec, we need to take into + * account the fall time of SCL signal (tf). Default tf value + * should be 0.3 us, for safety. + */ + return ((ic_clk * (tLOW + tf) + 5000) / 10000) - 1 + offset; +} + +/** \brief block until the i2c bus is no longer active, or it times out + * + * \param i2c_base IO base address of the i2c controller + */ +static int i2c_wait_until_ready(u32 i2c_base) +{ + u32 loops = SMBUS_TIMEOUT; + + do { + udelay(1); + if (--loops == 0) + break; + } while (read32((const volatile void *)(i2c_base + IC_STATUS)) & + IC_STATUS_ACTIVITY); + + return loops ? 0 : -1; +} + +/** + * \brief read or write to the i2c mmio addresses + * + * @param i2c_base mmio base address + * @param device 8 bit i2c device address + * @param addr_dat 8 or 16-bit address within the i2c device plus 8/16 bit flag + * @param data value to write to the register if this is a write + * @param command full or quick read/write command + * @return 8-bit value read from the device or negative value on error + */ +static s16 i2c_rw_byte(u32 i2c_base, u8 device, u32 addr_dat, u8 data, + u8 command) +{ + u32 input_clock_khz = 400; + + if (i2c_wait_until_ready(i2c_base) < 0) + return SMBUS_WAIT_UNTIL_READY_TIMEOUT; + + /*** Set up transaction ***/ + /* Disable i2c controller */ + write32((volatile void *)(i2c_base + IC_ENABLE), IC_ENABLE_DISABLE); + + /* + * set Standard-mode timings for high/low periods + * code from Linux/drivers/i2c/busses/i2c-designware-core.c + */ + write32((volatile void *)(i2c_base + IC_SS_SCL_HCNT), + i2c_dw_scl_hcnt( + input_clock_khz, + 40, /* tHD;STA = tHIGH = 4.0 us */ + 3, /* tf = 0.3 us */ + 0, /* 0: DW default, 1: Ideal */ + 0)); /* No offset */ + write32((volatile void *)(i2c_base + IC_SS_SCL_LCNT), + i2c_dw_scl_lcnt( + input_clock_khz, + 47, /* tLOW = 4.7 us */ + 3, /* tf = 0.3 us */ + 0)); /* No offset */ + + write32((volatile void *)(i2c_base + IC_CON), IC_CON_MASTER_MODE | + IC_CON_RESTART_EN | IC_CON_SLAVE_DISABLE | + IC_CON_7BITADDR_MASTER); + + /* Set the 7-bit i2c device address */ + write32((volatile void *)(i2c_base + IC_TAR), device >> 1 | + IC_TAR_7BITADDR_MASTER); + + /* Enable i2c controller */ + write32((volatile void *)(i2c_base + IC_ENABLE), IC_ENABLE_ENABLE); + + if (i2c_wait_until_ready(i2c_base) < 0) + return SMBUS_WAIT_UNTIL_READY_TIMEOUT; + + /* Set the address if this isn't a 'quick' transaction */ + if (command == FULL_RW) { + /* Set the address and data bytes */ + + if (addr_dat & ADDRESS_16_BIT) { + write32((volatile void *)(i2c_base + IC_DATA_CMD), + ((addr_dat >> 8) & 0x3f) | IC_DATA_CMD_WRITE); + + write32((volatile void *)(i2c_base + IC_DATA_CMD), + ((addr_dat & 0xff) | IC_DATA_CMD_WRITE)); + } else { + write32((volatile void *)(i2c_base + IC_DATA_CMD), + ((addr_dat & 0xff) | IC_DATA_CMD_WRITE)); + } + } + + /* send the data & write or the read command and set the stop bit */ + if (device & READ_BIT) + write32((volatile void *)(i2c_base + IC_DATA_CMD), + IC_DATA_CMD_READ | IC_DATA_CMD_STOP); + else + write32((volatile void *)(i2c_base + IC_DATA_CMD), data | + IC_DATA_CMD_WRITE | IC_DATA_CMD_STOP); + + if (i2c_wait_until_ready(i2c_base) < 0) + return SMBUS_WAIT_UNTIL_READY_TIMEOUT; + + if (device & READ_BIT) + return (read32((const volatile void *)(i2c_base + IC_DATA_CMD)) + & 0xff); + else + return SMBUS_SUCCESS; +} + +/** \brief generic smbus helper function to read & write to the smbus + * + * \details Configures the SMBus for the transaction, sets up the address + * and data bytes, starts the command, waits for the command to + * finish, and returns data if the command is a read. + * + * \param smbus_base The base address of the SMBUS controller + * + * \param device The 8-bit i2c device address, with the read / write bit already + * configured. + * + * \param addr_dat For full reads/writes, this contains the address within + * the device of the byte being read/written. For quick writes, + * this contains the data to write. + * + * \param data For full writes, this contains the Data to write to the + * device. For all other transactions, this is ignored. + * + * \param command Contains the command for a full read/write (0x48) or the + * command for a quick read/write (0x44) + * + * \return Data read from the device, or -1 if there was an error + */ +static s16 smbus_rw_byte(u16 smbus_base, u8 device, u32 addr_dat, u8 data, u8 command) +{ + u8 global_status_register; + + if (smbus_wait_until_ready(smbus_base) < 0) { + smbus_clear_inuse(smbus_base); + return SMBUS_WAIT_UNTIL_READY_TIMEOUT; + } + /*** Set up transaction ***/ + + /* Disable interrupts */ + outb(inb(smbus_base + SMBHSTCTL) & (~1), smbus_base + SMBHSTCTL); + + /* Set the device being talked to using supplied device address*/ + outb(device, smbus_base + SMBXMITADD); + + /* Set the address and data bytes */ + outb(addr_dat & 0xff, smbus_base + SMBHSTCMD); + if (addr_dat & ADDRESS_16_BIT) { + outb((addr_dat >> 8) & 0xff, smbus_base + SMBHSTDAT0); + outb(data, smbus_base + SMBHSTDAT1); + } else { + outb(data, smbus_base + SMBHSTDAT0); + } + + /* Clear any lingering errors, so the transaction will run */ + outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT); + + /* Start the command */ + outb(command, smbus_base + SMBHSTCTL); + + /* Poll for transaction completion */ + if (smbus_wait_until_done(smbus_base) < 0) { + smbus_clear_inuse(smbus_base); + return SMBUS_WAIT_UNTIL_DONE_TIMEOUT; + } + + global_status_register = inb(smbus_base + SMBHSTSTAT); + + /* Ignore the "In Use" status... */ + global_status_register &= ~(HSTSTS_SMBALERT_STS | HSTSTS_INUSE_STS); + + /* + * Read results + * INTR gets set when a command is completed successfully + */ + data = inb(smbus_base + SMBHSTDAT0); + if (global_status_register != HSTSTS_INTR) { + smbus_clear_inuse(smbus_base); + return SMBUS_ERROR; + } + + smbus_clear_inuse(smbus_base); + + return data; +} + +/** \brief Sends an address and writes one byte of data + * + * \param smbus_base The base address of the SMBUS controller + * + * \param device The 7-bit address of the device being written to + * + * \param address The address within the device to write the data to + * + * \param data The data value to write to the device + * + * \return negative value if there was an error + */ +s16 baytrail_smbus_write_byte(u16 smbus_base, u8 device, u8 address, u8 data) +{ + if (smbus_base & SMBUS_IO_ROUTINES) { + return (smbus_rw_byte((u16)(smbus_base & 0xfff0), device << 1, + address, data, FULL_RW)); + } else { + return (i2c_rw_byte(smbus_base, device << 1, address, data, + FULL_RW)); + } +} + +/** \brief Sends an address and reads one byte of data + * + * \param smbus_base The base address of the SMBUS controller + * + * \param device The 7-bit address of the device being written to + * + * \param address The address within the device to write the data to + * + * \return Data read from the device, or negative value if there was an error + */ +s16 baytrail_smbus_read_byte(u32 smbus_base, u8 device, u8 address) +{ + if (smbus_base & SMBUS_IO_ROUTINES) { + return (smbus_rw_byte((u16)(smbus_base & 0xfff0), + (device << 1) | READ_BIT, address, IGNORED_BYTE, + FULL_RW)); + } else { + return (i2c_rw_byte(smbus_base, (device << 1) | READ_BIT, + address, IGNORED_BYTE, FULL_RW)); + } +} + +/** \brief Sends one byte of data with no address byte + * + * \param smbus_base The base address of the SMBUS controller + * + * \param device The 7-bit address of the device being written to + * + * \param data The data value to write to the device + * + * \return negative value if there was an error + */ +s16 baytrail_smbus_quick_write(u32 smbus_base, u8 device, u8 data) +{ + if (smbus_base & SMBUS_IO_ROUTINES) { + return (smbus_rw_byte((u16)(smbus_base & 0xfff0), device << 1, + data, IGNORED_BYTE, QUICK_RW)); + } else { + return (i2c_rw_byte(smbus_base, device << 1, data, IGNORED_BYTE, + QUICK_RW)); + } + +} + +/** \brief Reads one byte of data without sending an address byte + * + * \param smbus_base The base address of the SMBUS controller + * + * \param device The 7-bit address of the device being written to + * + * \return Data read from the device, or negative value if there was an error + */ +s16 baytrail_smbus_quick_read(u32 smbus_base, u8 device) +{ + if (smbus_base & SMBUS_IO_ROUTINES) { + return (smbus_rw_byte((u16)(smbus_base & 0xfff0), + (device << 1) | READ_BIT, + IGNORED_BYTE, IGNORED_BYTE, QUICK_RW)); + } else { + return (i2c_rw_byte(smbus_base, (device << 1) | READ_BIT, + IGNORED_BYTE, IGNORED_BYTE, QUICK_RW)); + } + +} + +/** \brief Sends a 2-byte address and writes one byte of data + * + * \param smbus_base The base address of the SMBUS controller + * + * \param device The 7-bit address of the device being written to + * + * \param address The address within the device to write the data to + * + * \param data The data value to write to the device + * + * \return negative value if there was an error + */ +s16 baytrail_smbus_extended_write_byte(u32 smbus_base, u8 device, u16 address, + u8 data) +{ + u32 addr_dat = ADDRESS_16_BIT | address; + + if (smbus_base & SMBUS_IO_ROUTINES) + return (smbus_rw_byte((u16)(smbus_base & 0xfff0), device << 1, + addr_dat, data, FULL_RW)); + else + return (i2c_rw_byte(smbus_base, (device << 1), addr_dat, data, + FULL_RW)); +} + +/** \brief Sends a 2-byte address and reads one byte of data + * + * \param smbus_base The base address of the SMBUS controller + * + * \param device The 7-bit address of the device being written to + * + * \param address The address within the device to write the data to + * + * \return Data read from the device, or negative value if there was an error + */ +s16 baytrail_smbus_extended_read_byte(u32 smbus_base, u8 device, u16 address) +{ + s16 retval = 0; + u32 addr_dat = ADDRESS_16_BIT | address; + + if (smbus_base & SMBUS_IO_ROUTINES) { + retval = smbus_rw_byte((u16)(smbus_base & 0xfff0), + (device << 1) | READ_BIT, addr_dat, + IGNORED_BYTE, FULL_RW); + } else { + retval = i2c_rw_byte(smbus_base, (device << 1) | READ_BIT, + addr_dat, IGNORED_BYTE, FULL_RW); + } + return retval; +}