drivers: smbus: Add Intel ICH / PCH SMbus driver
Add driver supporting Intel I/O Controller Hub (ICH), later renamed to Intel Platform Controller Hub (PCH). Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
This commit is contained in:
parent
af984aa58b
commit
4099bc2e63
3 changed files with 848 additions and 0 deletions
|
@ -3,5 +3,6 @@
|
||||||
zephyr_library()
|
zephyr_library()
|
||||||
|
|
||||||
zephyr_library_sources_ifdef(CONFIG_SMBUS_SHELL smbus_shell.c)
|
zephyr_library_sources_ifdef(CONFIG_SMBUS_SHELL smbus_shell.c)
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_SMBUS_INTEL_PCH intel_pch_smbus.c)
|
||||||
|
|
||||||
zephyr_library_sources_ifdef(CONFIG_USERSPACE smbus_handlers.c)
|
zephyr_library_sources_ifdef(CONFIG_USERSPACE smbus_handlers.c)
|
||||||
|
|
|
@ -34,4 +34,34 @@ module = SMBUS
|
||||||
module-str = smbus
|
module-str = smbus
|
||||||
source "subsys/logging/Kconfig.template.log_config"
|
source "subsys/logging/Kconfig.template.log_config"
|
||||||
|
|
||||||
|
config SMBUS_INTEL_PCH
|
||||||
|
bool "SMBus Intel PCH driver"
|
||||||
|
default y
|
||||||
|
depends on DT_HAS_INTEL_PCH_SMBUS_ENABLED
|
||||||
|
help
|
||||||
|
Enable Intel Platform Controller Hub (PCH) SMBus driver.
|
||||||
|
|
||||||
|
if SMBUS_INTEL_PCH
|
||||||
|
|
||||||
|
choice SMBUS_INTEL_PCH_ACCESS
|
||||||
|
bool "SMBus register access mode"
|
||||||
|
default SMBUS_INTEL_PCH_ACCESS_IO
|
||||||
|
help
|
||||||
|
Default PCH register access mode. Set default access IO so
|
||||||
|
that both Qemu Q35 and Intel hardware are supported.
|
||||||
|
|
||||||
|
config SMBUS_INTEL_PCH_ACCESS_IO
|
||||||
|
bool "I/O PCH SMBus Register Access Mode"
|
||||||
|
help
|
||||||
|
Access PCH SMBus registers through I/O space.
|
||||||
|
|
||||||
|
config SMBUS_INTEL_PCH_ACCESS_MMIO
|
||||||
|
bool "MMIO PCH SMBus Register Access Mode"
|
||||||
|
help
|
||||||
|
Access PCH SMBus registers though MMIO space.
|
||||||
|
|
||||||
|
endchoice
|
||||||
|
|
||||||
|
endif # SMBUS_INTEL_PCH
|
||||||
|
|
||||||
endif # SMBUS
|
endif # SMBUS
|
||||||
|
|
817
drivers/smbus/intel_pch_smbus.c
Normal file
817
drivers/smbus/intel_pch_smbus.c
Normal file
|
@ -0,0 +1,817 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Intel Corporation
|
||||||
|
*
|
||||||
|
* Intel I/O Controller Hub (ICH) later renamed to Intel Platform Controller
|
||||||
|
* Hub (PCH) SMbus driver.
|
||||||
|
*
|
||||||
|
* PCH provides SMBus 2.0 - compliant Host Controller.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/types.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
|
||||||
|
#include <zephyr/drivers/smbus.h>
|
||||||
|
#include <zephyr/drivers/pcie/pcie.h>
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT intel_pch_smbus
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(intel_pch, CONFIG_SMBUS_LOG_LEVEL);
|
||||||
|
|
||||||
|
#include "intel_pch_smbus.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note Following notions are used:
|
||||||
|
* * periph_addr - Peripheral address (Slave address mentioned in the Specs)
|
||||||
|
* * command - First byte to send in the SMBus protocol operations except for
|
||||||
|
* Quick and Byte Read. Also known as register.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intel PCH configuration acquired from DTS during device initialization
|
||||||
|
*/
|
||||||
|
struct pch_config {
|
||||||
|
/* IRQ configuration function */
|
||||||
|
void (*config_func)(const struct device *dev);
|
||||||
|
/* PCIE BDF got from DTS */
|
||||||
|
pcie_bdf_t pcie_bdf;
|
||||||
|
/* PCIE ID got from DTS */
|
||||||
|
pcie_id_t pcie_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intel PCH internal driver data
|
||||||
|
*/
|
||||||
|
struct pch_data {
|
||||||
|
DEVICE_MMIO_RAM;
|
||||||
|
io_port_t sba;
|
||||||
|
uint32_t config;
|
||||||
|
uint8_t status;
|
||||||
|
|
||||||
|
struct k_mutex mutex;
|
||||||
|
struct k_sem completion_sync;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helpers for accessing Intel PCH SMBus registers. Depending on
|
||||||
|
* configuration option MMIO or IO method will be used.
|
||||||
|
*/
|
||||||
|
#if defined(CONFIG_SMBUS_INTEL_PCH_ACCESS_MMIO)
|
||||||
|
static uint8_t pch_reg_read(const struct device *dev, uint8_t reg)
|
||||||
|
{
|
||||||
|
return sys_read8(DEVICE_MMIO_GET(dev) + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pch_reg_write(const struct device *dev, uint8_t reg, uint8_t val)
|
||||||
|
{
|
||||||
|
sys_write8(val, DEVICE_MMIO_GET(dev) + reg);
|
||||||
|
}
|
||||||
|
#elif defined(CONFIG_SMBUS_INTEL_PCH_ACCESS_IO)
|
||||||
|
static uint8_t pch_reg_read(const struct device *dev, uint8_t reg)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
|
||||||
|
return sys_in8(data->sba + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pch_reg_write(const struct device *dev, uint8_t reg, uint8_t val)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
|
||||||
|
sys_out8(val, data->sba + reg);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error Wrong PCH Register Access Mode
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int pch_configure(const struct device *dev, uint32_t config)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p config 0x%x", dev, config);
|
||||||
|
|
||||||
|
/* Keep config for a moment */
|
||||||
|
data->config = config;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pch_get_config(const struct device *dev, uint32_t *config)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
|
||||||
|
*config = data->config;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Device initialization function */
|
||||||
|
static int pch_smbus_init(const struct device *dev)
|
||||||
|
{
|
||||||
|
const struct pch_config * const config = dev->config;
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
struct pcie_bar mbar;
|
||||||
|
uint32_t val;
|
||||||
|
|
||||||
|
if (!pcie_probe(config->pcie_bdf, config->pcie_id)) {
|
||||||
|
LOG_ERR("Cannot probe PCI device");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
val = pcie_conf_read(config->pcie_bdf, PCIE_CONF_CMDSTAT);
|
||||||
|
if (val & PCIE_CONF_CMDSTAT_INTERRUPT) {
|
||||||
|
LOG_WRN("Pending interrupt, continuing");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_SMBUS_INTEL_PCH_ACCESS_MMIO)) {
|
||||||
|
pcie_probe_mbar(config->pcie_bdf, 0, &mbar);
|
||||||
|
pcie_set_cmd(config->pcie_bdf, PCIE_CONF_CMDSTAT_MEM, true);
|
||||||
|
|
||||||
|
device_map(DEVICE_MMIO_RAM_PTR(dev), mbar.phys_addr, mbar.size,
|
||||||
|
K_MEM_CACHE_NONE);
|
||||||
|
|
||||||
|
LOG_DBG("Mapped 0x%lx size 0x%lx to 0x%lx",
|
||||||
|
mbar.phys_addr, mbar.size, DEVICE_MMIO_GET(dev));
|
||||||
|
} else {
|
||||||
|
pcie_set_cmd(config->pcie_bdf, PCIE_CONF_CMDSTAT_IO, true);
|
||||||
|
val = pcie_conf_read(config->pcie_bdf, PCIE_CONF_BAR4);
|
||||||
|
if (!PCIE_CONF_BAR_IO(val)) {
|
||||||
|
LOG_ERR("Cannot read IO BAR");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->sba = PCIE_CONF_BAR_ADDR(val);
|
||||||
|
|
||||||
|
LOG_DBG("Using I/O address 0x%x", data->sba);
|
||||||
|
}
|
||||||
|
|
||||||
|
val = pcie_conf_read(config->pcie_bdf, PCH_SMBUS_HCFG);
|
||||||
|
if ((val & PCH_SMBUS_HCFG_HST_EN) == 0) {
|
||||||
|
LOG_ERR("SMBus Host Controller is disabled");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize mutex and semaphore */
|
||||||
|
k_mutex_init(&data->mutex);
|
||||||
|
k_sem_init(&data->completion_sync, 0, 1);
|
||||||
|
|
||||||
|
config->config_func(dev);
|
||||||
|
|
||||||
|
if (pch_configure(dev, SMBUS_MODE_CONTROLLER)) {
|
||||||
|
LOG_ERR("SMBus: Cannot set default configuration");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pch_prepare_transfer(const struct device *dev)
|
||||||
|
{
|
||||||
|
uint8_t hsts;
|
||||||
|
|
||||||
|
hsts = pch_reg_read(dev, PCH_SMBUS_HSTS);
|
||||||
|
|
||||||
|
pch_dump_register_hsts(hsts);
|
||||||
|
|
||||||
|
if (hsts & PCH_SMBUS_HSTS_HOST_BUSY) {
|
||||||
|
LOG_ERR("Return BUSY status");
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check and clear HSTS status bits */
|
||||||
|
hsts &= PCH_SMBUS_HSTS_ERROR | PCH_SMBUS_HSTS_BYTE_DONE |
|
||||||
|
PCH_SMBUS_HSTS_INTERRUPT;
|
||||||
|
if (hsts) {
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HSTS, hsts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Clear also CRC check bits */
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pch_check_status(const struct device *dev)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
uint8_t status = data->status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device Error means following:
|
||||||
|
* - unsupported Command Field Unclaimed Cycle
|
||||||
|
* - Host Device timeout
|
||||||
|
* - CRC Error
|
||||||
|
*/
|
||||||
|
if (status & PCH_SMBUS_HSTS_DEV_ERROR) {
|
||||||
|
uint8_t auxs = pch_reg_read(dev, PCH_SMBUS_AUXS);
|
||||||
|
|
||||||
|
LOG_WRN("Device Error (DERR) received");
|
||||||
|
|
||||||
|
if (auxs & PCH_SMBUS_AUXS_CRC_ERROR) {
|
||||||
|
LOG_DBG("AUXS register 0x%02x", auxs);
|
||||||
|
/* Clear CRC error status bit */
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_AUXS,
|
||||||
|
PCH_SMBUS_AUXS_CRC_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction collision, several masters are trying to access
|
||||||
|
* the bus and PCH detects arbitration lost.
|
||||||
|
*/
|
||||||
|
if (status & PCH_SMBUS_HSTS_BUS_ERROR) {
|
||||||
|
LOG_WRN("Bus Error (BERR) received");
|
||||||
|
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of interrupt is failed bus transaction. This is set in
|
||||||
|
* response to KILL set to terminate the host transaction
|
||||||
|
*/
|
||||||
|
if (status & PCH_SMBUS_HSTS_FAILED) {
|
||||||
|
LOG_WRN("Failed (FAIL) received");
|
||||||
|
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pch_smbus_block_start(const struct device *dev, uint16_t periph_addr,
|
||||||
|
uint8_t rw, uint8_t command, uint8_t count,
|
||||||
|
uint8_t *buf, uint8_t protocol)
|
||||||
|
{
|
||||||
|
uint8_t reg;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("addr %x rw %d command %x", periph_addr, rw, command);
|
||||||
|
|
||||||
|
/* Set TSA register */
|
||||||
|
reg = PCH_SMBUS_TSA_ADDR_SET(periph_addr);
|
||||||
|
reg |= rw & SMBUS_MSG_RW_MASK;
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_TSA, reg);
|
||||||
|
|
||||||
|
/* Set HCMD register */
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HCMD, command);
|
||||||
|
|
||||||
|
/* Enable 32-byte buffer mode (E32b) to send block of data */
|
||||||
|
reg = pch_reg_read(dev, PCH_SMBUS_AUXC);
|
||||||
|
reg |= PCH_SMBUS_AUXC_EN_32BUF;
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_AUXC, reg);
|
||||||
|
|
||||||
|
/* In E32B mode read and write to PCH_SMBUS_HBD translates to
|
||||||
|
* read and write to 32 byte storage array, index needs to be
|
||||||
|
* cleared by reading HCTL
|
||||||
|
*/
|
||||||
|
reg = pch_reg_read(dev, PCH_SMBUS_HCTL);
|
||||||
|
ARG_UNUSED(reg); /* Avoid 'Dead assignment' warning */
|
||||||
|
|
||||||
|
if (rw == SMBUS_MSG_WRITE) {
|
||||||
|
/* Write count */
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HD0, count);
|
||||||
|
|
||||||
|
/* Write data to send */
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HBD, buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_prepare_transfer(dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set HCTL register */
|
||||||
|
reg = PCH_SMBUS_HCTL_CMD_SET(protocol);
|
||||||
|
reg |= PCH_SMBUS_HCTL_START;
|
||||||
|
reg |= PCH_SMBUS_HCTL_INTR_EN;
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HCTL, reg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start PCH SMBus operation */
|
||||||
|
static int pch_smbus_start(const struct device *dev, uint16_t periph_addr,
|
||||||
|
enum smbus_direction rw, uint8_t command,
|
||||||
|
uint8_t *buf, uint8_t protocol)
|
||||||
|
{
|
||||||
|
uint8_t reg;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("addr %x rw %d command %x", periph_addr, rw, command);
|
||||||
|
|
||||||
|
/* Set TSA register */
|
||||||
|
reg = PCH_SMBUS_TSA_ADDR_SET(periph_addr);
|
||||||
|
reg |= rw & SMBUS_MSG_RW_MASK;
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_TSA, reg);
|
||||||
|
|
||||||
|
/* Write command for every but QUICK op */
|
||||||
|
if (protocol != SMBUS_CMD_QUICK) {
|
||||||
|
/* Set HCMD register */
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HCMD, command);
|
||||||
|
|
||||||
|
/* Set Host Data 0 (HD0) register */
|
||||||
|
if (rw == SMBUS_MSG_WRITE && protocol != SMBUS_CMD_BYTE) {
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HD0, buf[0]);
|
||||||
|
|
||||||
|
/* If we need to write second byte */
|
||||||
|
if (protocol == SMBUS_CMD_WORD_DATA ||
|
||||||
|
protocol == SMBUS_CMD_PROC_CALL) {
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HD1, buf[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_prepare_transfer(dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set HCTL register */
|
||||||
|
reg = PCH_SMBUS_HCTL_CMD_SET(protocol);
|
||||||
|
reg |= PCH_SMBUS_HCTL_START;
|
||||||
|
reg |= PCH_SMBUS_HCTL_INTR_EN;
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HCTL, reg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of PCH SMBus API */
|
||||||
|
|
||||||
|
/* Implementation of SMBus Quick */
|
||||||
|
static int pch_smbus_quick(const struct device *dev, uint16_t periph_addr,
|
||||||
|
enum smbus_direction rw)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x direction %x", dev, periph_addr, rw);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_start(dev, periph_addr, rw, 0, NULL, SMBUS_CMD_QUICK);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Quick timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of SMBus Byte Write */
|
||||||
|
static int pch_smbus_byte_write(const struct device *dev, uint16_t periph_addr,
|
||||||
|
uint8_t command)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x command 0x%02x", dev, periph_addr, command);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_start(dev, periph_addr, SMBUS_MSG_WRITE, command, NULL,
|
||||||
|
SMBUS_CMD_BYTE);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Byte Write timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of SMBus Byte Read */
|
||||||
|
static int pch_smbus_byte_read(const struct device *dev, uint16_t periph_addr,
|
||||||
|
uint8_t *byte)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x", dev, periph_addr);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_start(dev, periph_addr, SMBUS_MSG_READ, 0, NULL,
|
||||||
|
SMBUS_CMD_BYTE);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Byte Read timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
*byte = pch_reg_read(dev, PCH_SMBUS_HD0);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of SMBus Byte Data Write */
|
||||||
|
static int pch_smbus_byte_data_write(const struct device *dev,
|
||||||
|
uint16_t periph_addr,
|
||||||
|
uint8_t command, uint8_t byte)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x command 0x%02x", dev, periph_addr, command);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_start(dev, periph_addr, SMBUS_MSG_WRITE, command,
|
||||||
|
&byte, SMBUS_CMD_BYTE_DATA);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Byte Data Write timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of SMBus Byte Data Read */
|
||||||
|
static int pch_smbus_byte_data_read(const struct device *dev,
|
||||||
|
uint16_t periph_addr,
|
||||||
|
uint8_t command, uint8_t *byte)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x command 0x%02x", dev, periph_addr, command);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_start(dev, periph_addr, SMBUS_MSG_READ, command,
|
||||||
|
NULL, SMBUS_CMD_BYTE_DATA);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Byte Data Read timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
*byte = pch_reg_read(dev, PCH_SMBUS_HD0);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of SMBus Word Data Write */
|
||||||
|
static int pch_smbus_word_data_write(const struct device *dev,
|
||||||
|
uint16_t periph_addr,
|
||||||
|
uint8_t command, uint16_t word)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x command 0x%02x", dev, periph_addr, command);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_start(dev, periph_addr, SMBUS_MSG_WRITE, command,
|
||||||
|
(uint8_t *)&word, SMBUS_CMD_WORD_DATA);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Word Data Write timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of SMBus Word Data Read */
|
||||||
|
static int pch_smbus_word_data_read(const struct device *dev,
|
||||||
|
uint16_t periph_addr,
|
||||||
|
uint8_t command, uint16_t *word)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
uint8_t *p = (uint8_t *)word;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x command 0x%02x", dev, periph_addr, command);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_start(dev, periph_addr, SMBUS_MSG_READ, command,
|
||||||
|
NULL, SMBUS_CMD_WORD_DATA);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Word Data Read timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
p[0] = pch_reg_read(dev, PCH_SMBUS_HD0);
|
||||||
|
p[1] = pch_reg_read(dev, PCH_SMBUS_HD1);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of SMBus Process Call */
|
||||||
|
static int pch_smbus_pcall(const struct device *dev,
|
||||||
|
uint16_t periph_addr, uint8_t command,
|
||||||
|
uint16_t send_word, uint16_t *recv_word)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
uint8_t *p = (uint8_t *)recv_word;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x command 0x%02x", dev, periph_addr, command);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_start(dev, periph_addr, SMBUS_MSG_WRITE, command,
|
||||||
|
(uint8_t *)&send_word, SMBUS_CMD_PROC_CALL);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Proc Call timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
p[0] = pch_reg_read(dev, PCH_SMBUS_HD0);
|
||||||
|
p[1] = pch_reg_read(dev, PCH_SMBUS_HD1);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of SMBus Block Write */
|
||||||
|
static int pch_smbus_block_write(const struct device *dev, uint16_t periph_addr,
|
||||||
|
uint8_t command, uint8_t count, uint8_t *buf)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x command 0x%02x count %u",
|
||||||
|
dev, periph_addr, command, count);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_block_start(dev, periph_addr, SMBUS_MSG_WRITE, command,
|
||||||
|
count, buf, SMBUS_CMD_BLOCK);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Block Write timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of SMBus Block Read */
|
||||||
|
static int pch_smbus_block_read(const struct device *dev, uint16_t periph_addr,
|
||||||
|
uint8_t command, uint8_t *count, uint8_t *buf)
|
||||||
|
{
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
LOG_DBG("dev %p addr 0x%02x command 0x%02x",
|
||||||
|
dev, periph_addr, command);
|
||||||
|
|
||||||
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
||||||
|
|
||||||
|
ret = pch_smbus_block_start(dev, periph_addr, SMBUS_MSG_READ, command,
|
||||||
|
0, buf, SMBUS_CMD_BLOCK);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for completion from ISR */
|
||||||
|
ret = k_sem_take(&data->completion_sync, K_MSEC(30));
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("SMBus Block Read timed out");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = pch_check_status(dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
*count = pch_reg_read(dev, PCH_SMBUS_HD0);
|
||||||
|
if (*count == 0 || *count > SMBUS_BLOCK_BYTES_MAX) {
|
||||||
|
ret = -ENODATA;
|
||||||
|
goto unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < *count; i++) {
|
||||||
|
buf[i] = pch_reg_read(dev, PCH_SMBUS_HBD);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
k_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct smbus_driver_api funcs = {
|
||||||
|
.configure = pch_configure,
|
||||||
|
.get_config = pch_get_config,
|
||||||
|
.smbus_quick = pch_smbus_quick,
|
||||||
|
.smbus_byte_write = pch_smbus_byte_write,
|
||||||
|
.smbus_byte_read = pch_smbus_byte_read,
|
||||||
|
.smbus_byte_data_write = pch_smbus_byte_data_write,
|
||||||
|
.smbus_byte_data_read = pch_smbus_byte_data_read,
|
||||||
|
.smbus_word_data_write = pch_smbus_word_data_write,
|
||||||
|
.smbus_word_data_read = pch_smbus_word_data_read,
|
||||||
|
.smbus_pcall = pch_smbus_pcall,
|
||||||
|
.smbus_block_write = pch_smbus_block_write,
|
||||||
|
.smbus_block_read = pch_smbus_block_read,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void smbus_isr(const struct device *dev)
|
||||||
|
{
|
||||||
|
const struct pch_config * const config = dev->config;
|
||||||
|
struct pch_data *data = dev->data;
|
||||||
|
uint32_t sts;
|
||||||
|
uint8_t status;
|
||||||
|
|
||||||
|
sts = pcie_conf_read(config->pcie_bdf, PCIE_CONF_CMDSTAT);
|
||||||
|
if (!(sts & PCIE_CONF_CMDSTAT_INTERRUPT)) {
|
||||||
|
LOG_ERR("Not our interrupt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = pch_reg_read(dev, PCH_SMBUS_HSTS);
|
||||||
|
|
||||||
|
/* HSTS dump if logging is enabled */
|
||||||
|
pch_dump_register_hsts(status);
|
||||||
|
|
||||||
|
if (status & PCH_SMBUS_HSTS_BYTE_DONE) {
|
||||||
|
LOG_WRN("BYTE_DONE interrupt is not used");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear IRQ sources */
|
||||||
|
pch_reg_write(dev, PCH_SMBUS_HSTS, status);
|
||||||
|
|
||||||
|
data->status = status;
|
||||||
|
|
||||||
|
k_sem_give(&data->completion_sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Device macro initialization / DTS hackery */
|
||||||
|
|
||||||
|
/* PCI BDF is hacked into REG_ADDR */
|
||||||
|
#define DT_INST_PCIE_BDF(n) DT_INST_REG_ADDR(n)
|
||||||
|
|
||||||
|
/* PCI ID is hacked into REG_SIZE */
|
||||||
|
#define DT_INST_PCIE_ID(n) DT_INST_REG_SIZE(n)
|
||||||
|
|
||||||
|
#define SMBUS_PCH_IRQ_FLAGS_SENSE0(n) 0
|
||||||
|
#define SMBUS_PCH_IRQ_FLAGS_SENSE1(n) DT_INST_IRQ(n, sense)
|
||||||
|
#define SMBUS_PCH_IRQ_FLAGS(n) \
|
||||||
|
_CONCAT(SMBUS_PCH_IRQ_FLAGS_SENSE, DT_INST_IRQ_HAS_CELL(n, sense))(n)
|
||||||
|
|
||||||
|
#define SMBUS_IRQ_CONFIG(n) \
|
||||||
|
BUILD_ASSERT(IS_ENABLED(CONFIG_DYNAMIC_INTERRUPTS), \
|
||||||
|
"SMBus PCIe requires dynamic interrupts"); \
|
||||||
|
static void pch_config_##n(const struct device *dev) \
|
||||||
|
{ \
|
||||||
|
ARG_UNUSED(dev); \
|
||||||
|
unsigned int irq; \
|
||||||
|
if (DT_INST_IRQN(n) == PCIE_IRQ_DETECT) { \
|
||||||
|
irq = pcie_alloc_irq(DT_INST_PCIE_BDF(n)); \
|
||||||
|
if (irq == PCIE_CONF_INTR_IRQ_NONE) { \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} else { \
|
||||||
|
irq = DT_INST_IRQN(n); \
|
||||||
|
pcie_conf_write(DT_INST_PCIE_BDF(n), \
|
||||||
|
PCIE_CONF_INTR, irq); \
|
||||||
|
} \
|
||||||
|
pcie_connect_dynamic_irq(DT_INST_PCIE_BDF(n), irq, \
|
||||||
|
DT_INST_IRQ(n, priority), \
|
||||||
|
(void (*)(const void *))smbus_isr, \
|
||||||
|
DEVICE_DT_INST_GET(n), \
|
||||||
|
SMBUS_PCH_IRQ_FLAGS(n)); \
|
||||||
|
pcie_irq_enable(DT_INST_PCIE_BDF(n), irq); \
|
||||||
|
LOG_DBG("Configure irq %d", irq); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SMBUS_DEVICE_INIT(n) \
|
||||||
|
static void pch_config_##n(const struct device *dev); \
|
||||||
|
static const struct pch_config pch_config_data_##n = { \
|
||||||
|
.config_func = pch_config_##n, \
|
||||||
|
.pcie_bdf = DT_INST_PCIE_BDF(n), \
|
||||||
|
.pcie_id = DT_INST_PCIE_ID(n), \
|
||||||
|
}; \
|
||||||
|
static struct pch_data smbus_##n##_data; \
|
||||||
|
SMBUS_DEVICE_DT_INST_DEFINE(n, pch_smbus_init, NULL, \
|
||||||
|
&smbus_##n##_data, &pch_config_data_##n, \
|
||||||
|
POST_KERNEL, CONFIG_SMBUS_INIT_PRIORITY, \
|
||||||
|
&funcs); \
|
||||||
|
SMBUS_IRQ_CONFIG(n);
|
||||||
|
|
||||||
|
DT_INST_FOREACH_STATUS_OKAY(SMBUS_DEVICE_INIT)
|
Loading…
Add table
Add a link
Reference in a new issue