| /* |
| * 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 "smbus_utils.h" |
| #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); |
| struct pcie_dev *pcie; |
| }; |
| |
| /** |
| * 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; |
| const struct device *dev; |
| |
| #if defined(CONFIG_SMBUS_INTEL_PCH_SMBALERT) |
| /* smbalert callback list */ |
| sys_slist_t smbalert_cbs; |
| /* smbalert work */ |
| struct k_work smb_alert_work; |
| #endif /* CONFIG_SMBUS_INTEL_PCH_SMBALERT */ |
| |
| #if defined(CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY) |
| /* Host Notify callback list */ |
| sys_slist_t host_notify_cbs; |
| /* Host Notify work */ |
| struct k_work host_notify_work; |
| /* Host Notify peripheral device address */ |
| uint8_t notify_addr; |
| /* Host Notify data received */ |
| uint16_t notify_data; |
| #endif /* CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY */ |
| }; |
| |
| /** |
| * 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 |
| |
| #if defined(CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY) |
| static void host_notify_work(struct k_work *work) |
| { |
| struct pch_data *data = CONTAINER_OF(work, struct pch_data, |
| host_notify_work); |
| const struct device *dev = data->dev; |
| uint8_t addr = data->notify_addr; |
| |
| smbus_fire_callbacks(&data->host_notify_cbs, dev, addr); |
| } |
| |
| static int pch_smbus_host_notify_set_cb(const struct device *dev, |
| struct smbus_callback *cb) |
| { |
| struct pch_data *data = dev->data; |
| |
| LOG_DBG("dev %p cb %p", dev, cb); |
| |
| return smbus_callback_set(&data->host_notify_cbs, cb); |
| } |
| |
| static int pch_smbus_host_notify_remove_cb(const struct device *dev, |
| struct smbus_callback *cb) |
| { |
| struct pch_data *data = dev->data; |
| |
| LOG_DBG("dev %p cb %p", dev, cb); |
| |
| return smbus_callback_remove(&data->host_notify_cbs, cb); |
| } |
| #endif /* CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY */ |
| |
| #if defined(CONFIG_SMBUS_INTEL_PCH_SMBALERT) |
| static void smbalert_work(struct k_work *work) |
| { |
| struct pch_data *data = CONTAINER_OF(work, struct pch_data, |
| smb_alert_work); |
| const struct device *dev = data->dev; |
| |
| /** |
| * There might be several peripheral devices and the he highest |
| * priority (lowest address) device wins arbitration, we need to |
| * read them all. |
| * |
| * The format of the transaction is: |
| * |
| * 0 1 2 |
| * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| * |S| Alert Addr |R|A| Address |X|N|P| |
| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| */ |
| do { |
| uint8_t addr; |
| int ret; |
| |
| ret = smbus_byte_read(dev, SMBUS_ADDRESS_ARA, &addr); |
| if (ret < 0) { |
| LOG_DBG("Cannot read peripheral address (anymore)"); |
| return; |
| } |
| |
| LOG_DBG("Read addr 0x%02x, ret %d", addr, ret); |
| |
| smbus_fire_callbacks(&data->smbalert_cbs, dev, addr); |
| } while (true); |
| } |
| |
| static int pch_smbus_smbalert_set_sb(const struct device *dev, |
| struct smbus_callback *cb) |
| { |
| struct pch_data *data = dev->data; |
| |
| LOG_DBG("dev %p cb %p", dev, cb); |
| |
| return smbus_callback_set(&data->smbalert_cbs, cb); |
| } |
| |
| static int pch_smbus_smbalert_remove_sb(const struct device *dev, |
| struct smbus_callback *cb) |
| { |
| struct pch_data *data = dev->data; |
| |
| LOG_DBG("dev %p cb %p", dev, cb); |
| |
| return smbus_callback_remove(&data->smbalert_cbs, cb); |
| } |
| #endif /* CONFIG_SMBUS_INTEL_PCH_SMBALERT */ |
| |
| 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); |
| |
| if (config & SMBUS_MODE_HOST_NOTIFY) { |
| uint8_t status; |
| |
| if (!IS_ENABLED(CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY)) { |
| LOG_ERR("Error configuring Host Notify"); |
| return -EINVAL; |
| } |
| |
| /* Enable Host Notify interrupts */ |
| status = pch_reg_read(dev, PCH_SMBUS_SCMD); |
| status |= PCH_SMBUS_SCMD_HNI_EN; |
| pch_reg_write(dev, PCH_SMBUS_SCMD, status); |
| } |
| |
| if (config & SMBUS_MODE_SMBALERT) { |
| uint8_t status; |
| |
| if (!IS_ENABLED(CONFIG_SMBUS_INTEL_PCH_SMBALERT)) { |
| LOG_ERR("Error configuring SMBALERT"); |
| return -EINVAL; |
| } |
| |
| /* Disable SMBALERT_DIS */ |
| status = pch_reg_read(dev, PCH_SMBUS_SCMD); |
| status &= ~PCH_SMBUS_SCMD_SMBALERT_DIS; |
| pch_reg_write(dev, PCH_SMBUS_SCMD, status); |
| } |
| |
| /* 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 (config->pcie->bdf == PCIE_BDF_NONE) { |
| 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); |
| |
| data->dev = dev; |
| |
| /* Initialize work structures */ |
| |
| #if defined(CONFIG_SMBUS_INTEL_PCH_SMBALERT) |
| k_work_init(&data->smb_alert_work, smbalert_work); |
| #endif /* CONFIG_SMBUS_INTEL_PCH_SMBALERT */ |
| |
| #if defined(CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY) |
| k_work_init(&data->host_notify_work, host_notify_work); |
| #endif /* CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY */ |
| |
| 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 0x%02x 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; |
| } |
| |
| /* Implementation of SMBus Block Process Call */ |
| static int pch_smbus_block_pcall(const struct device *dev, |
| uint16_t periph_addr, uint8_t command, |
| uint8_t send_count, uint8_t *send_buf, |
| uint8_t *recv_count, uint8_t *recv_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_WRITE, command, |
| send_count, send_buf, SMBUS_CMD_BLOCK_PROC); |
| 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 Process Call timed out"); |
| ret = -ETIMEDOUT; |
| goto unlock; |
| } |
| |
| ret = pch_check_status(dev); |
| if (ret < 0) { |
| goto unlock; |
| } |
| |
| *recv_count = pch_reg_read(dev, PCH_SMBUS_HD0); |
| if (*recv_count == 0 || |
| *recv_count + send_count > SMBUS_BLOCK_BYTES_MAX) { |
| ret = -ENODATA; |
| goto unlock; |
| } |
| |
| for (int i = 0; i < *recv_count; i++) { |
| recv_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, |
| .smbus_block_pcall = pch_smbus_block_pcall, |
| #if defined(CONFIG_SMBUS_INTEL_PCH_SMBALERT) |
| .smbus_smbalert_set_cb = pch_smbus_smbalert_set_sb, |
| .smbus_smbalert_remove_cb = pch_smbus_smbalert_remove_sb, |
| #endif /* CONFIG_SMBUS_INTEL_PCH_SMBALERT */ |
| #if defined(CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY) |
| .smbus_host_notify_set_cb = pch_smbus_host_notify_set_cb, |
| .smbus_host_notify_remove_cb = pch_smbus_host_notify_remove_cb, |
| #endif /* CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY */ |
| }; |
| |
| 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; |
| } |
| |
| /** |
| * Handle first Host Notify since for that we need to read SSTS |
| * register and for all other sources HSTS. |
| * |
| * Intel PCH implements Host Notify protocol in hardware. |
| */ |
| #if defined(CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY) |
| if (data->config & SMBUS_MODE_HOST_NOTIFY) { |
| status = pch_reg_read(dev, PCH_SMBUS_SSTS); |
| if (status & PCH_SMBUS_SSTS_HNS) { |
| /* Notify address */ |
| data->notify_addr = |
| pch_reg_read(dev, PCH_SMBUS_NDA) >> 1; |
| |
| /* Notify data */ |
| data->notify_data = pch_reg_read(dev, PCH_SMBUS_NDLB); |
| data->notify_data |= |
| pch_reg_read(dev, PCH_SMBUS_NDHB) << 8; |
| |
| k_work_submit(&data->host_notify_work); |
| |
| /* Clear Host Notify */ |
| pch_reg_write(dev, PCH_SMBUS_SSTS, PCH_SMBUS_SSTS_HNS); |
| |
| return; |
| } |
| } |
| #endif /* CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY */ |
| |
| 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"); |
| } |
| |
| /* Handle SMBALERT# signal */ |
| #if defined(CONFIG_SMBUS_INTEL_PCH_SMBALERT) |
| if (data->config & SMBUS_MODE_SMBALERT && |
| status & PCH_SMBUS_HSTS_SMB_ALERT) { |
| k_work_submit(&data->smb_alert_work); |
| } |
| #endif /* CONFIG_SMBUS_INTEL_PCH_SMBALERT */ |
| |
| /* 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 */ |
| |
| #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) \ |
| { \ |
| const struct pch_config * const config = dev->config; \ |
| unsigned int irq; \ |
| if (DT_INST_IRQN(n) == PCIE_IRQ_DETECT) { \ |
| irq = pcie_alloc_irq(config->pcie->bdf); \ |
| if (irq == PCIE_CONF_INTR_IRQ_NONE) { \ |
| return; \ |
| } \ |
| } else { \ |
| irq = DT_INST_IRQN(n); \ |
| pcie_conf_write(config->pcie->bdf, \ |
| PCIE_CONF_INTR, irq); \ |
| } \ |
| pcie_connect_dynamic_irq(config->pcie->bdf, irq, \ |
| DT_INST_IRQ(n, priority), \ |
| (void (*)(const void *))smbus_isr, \ |
| DEVICE_DT_INST_GET(n), \ |
| SMBUS_PCH_IRQ_FLAGS(n)); \ |
| pcie_irq_enable(config->pcie->bdf, irq); \ |
| LOG_DBG("Configure irq %d", irq); \ |
| } |
| |
| #define SMBUS_DEVICE_INIT(n) \ |
| DEVICE_PCIE_INST_DECLARE(n); \ |
| static void pch_config_##n(const struct device *dev); \ |
| static const struct pch_config pch_config_data_##n = { \ |
| DEVICE_PCIE_INST_INIT(n, pcie), \ |
| .config_func = pch_config_##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) |