summaryrefslogtreecommitdiff
path: root/drivers/i2c/busses/i2c-amd-asf-plat.c
diff options
context:
space:
mode:
authorWolfram Sang <wsa+renesas@sang-engineering.com>2024-11-18 08:35:47 +0100
committerWolfram Sang <wsa+renesas@sang-engineering.com>2024-11-18 08:35:47 +0100
commit1b3073291ddbe23fede7e0dd1b6f5635e370f8ba (patch)
treea3245db38b3389d4a63731b2c679c2d38eb24026 /drivers/i2c/busses/i2c-amd-asf-plat.c
parent48730a9d04ffccda541602d722d1ff81920a85d8 (diff)
parent1922bc245541bd08e3282d8199c8ac703e366111 (diff)
downloadlwn-1b3073291ddbe23fede7e0dd1b6f5635e370f8ba.tar.gz
lwn-1b3073291ddbe23fede7e0dd1b6f5635e370f8ba.zip
Merge tag 'i2c-host-6.13-p1' of git://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux into i2c/for-mergewindow
i2c-host updates for v6.13, part 1 Major Improvements and Refactoring: - All controllers using the 'remove_new' callback have been reverted to use the 'remove' callback. - Intel SCH controller underwent significant refactoring, this brings love and a modern look to the driver. - PIIX4 driver refactored to enable usage by other drivers (e.g., AMD ASF). - iMX/MXC improved message handling to reduce protocol overhead: Refactored DMA/non-DMA read/write and bus polling mechanisms to achieve this. - ACPI documentation for PIIX4. New Features: - i2c-cadence added support for atomic transfers. - Qualcomm CII added support for a 32MHz serial engine clock. Deprecated Features: - Dropped outdated support for AMD756 S4882 and NFORCE2 S4985. If somebody misses this, Jean will rewrite support using the proper i2c mux framework. New Hardware Support: - Added support for: - Intel Panther Lake (new ID) - AMD ASF (new driver) - S32G2/S32G3 SoCs (new ID) - Realtek RTL I2C Controller (new driver) - HJMC01 DesignWare ACPI HID (new ID) - PIC64GX to Microchip Core (new ID) - Qualcomm SDM670 to Qualcomm CCI (new ID)
Diffstat (limited to 'drivers/i2c/busses/i2c-amd-asf-plat.c')
-rw-r--r--drivers/i2c/busses/i2c-amd-asf-plat.c369
1 files changed, 369 insertions, 0 deletions
diff --git a/drivers/i2c/busses/i2c-amd-asf-plat.c b/drivers/i2c/busses/i2c-amd-asf-plat.c
new file mode 100644
index 000000000000..ba47df5370c7
--- /dev/null
+++ b/drivers/i2c/busses/i2c-amd-asf-plat.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Alert Standard Format Platform Driver
+ *
+ * Copyright (c) 2024, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ * Sanket Goswami <Sanket.Goswami@amd.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/devm-helpers.h>
+#include <linux/errno.h>
+#include <linux/gfp_types.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/sprintf.h>
+
+#include "i2c-piix4.h"
+
+/* ASF register bits */
+#define ASF_SLV_LISTN 0
+#define ASF_SLV_INTR 1
+#define ASF_SLV_RST 4
+#define ASF_PEC_SP 5
+#define ASF_DATA_EN 7
+#define ASF_MSTR_EN 16
+#define ASF_CLK_EN 17
+
+/* ASF address offsets */
+#define ASFINDEX (0x07 + piix4_smba)
+#define ASFLISADDR (0x09 + piix4_smba)
+#define ASFSTA (0x0A + piix4_smba)
+#define ASFSLVSTA (0x0D + piix4_smba)
+#define ASFDATARWPTR (0x11 + piix4_smba)
+#define ASFSETDATARDPTR (0x12 + piix4_smba)
+#define ASFDATABNKSEL (0x13 + piix4_smba)
+#define ASFSLVEN (0x15 + piix4_smba)
+
+#define ASF_BLOCK_MAX_BYTES 72
+#define ASF_ERROR_STATUS GENMASK(3, 1)
+
+struct amd_asf_dev {
+ struct i2c_adapter adap;
+ void __iomem *eoi_base;
+ struct i2c_client *target;
+ struct delayed_work work_buf;
+ struct sb800_mmio_cfg mmio_cfg;
+ struct resource *port_addr;
+};
+
+static void amd_asf_process_target(struct work_struct *work)
+{
+ struct amd_asf_dev *dev = container_of(work, struct amd_asf_dev, work_buf.work);
+ unsigned short piix4_smba = dev->port_addr->start;
+ u8 data[ASF_BLOCK_MAX_BYTES];
+ u8 bank, reg, cmd;
+ u8 len = 0, idx, val;
+
+ /* Read target status register */
+ reg = inb_p(ASFSLVSTA);
+
+ /* Check if no error bits are set in target status register */
+ if (reg & ASF_ERROR_STATUS) {
+ /* Set bank as full */
+ cmd = 0;
+ reg |= GENMASK(3, 2);
+ outb_p(reg, ASFDATABNKSEL);
+ } else {
+ /* Read data bank */
+ reg = inb_p(ASFDATABNKSEL);
+ bank = (reg & BIT(3)) ? 1 : 0;
+
+ /* Set read data bank */
+ if (bank) {
+ reg |= BIT(4);
+ reg &= ~BIT(3);
+ } else {
+ reg &= ~BIT(4);
+ reg &= ~BIT(2);
+ }
+
+ /* Read command register */
+ outb_p(reg, ASFDATABNKSEL);
+ cmd = inb_p(ASFINDEX);
+ len = inb_p(ASFDATARWPTR);
+ for (idx = 0; idx < len; idx++)
+ data[idx] = inb_p(ASFINDEX);
+
+ /* Clear data bank status */
+ if (bank) {
+ reg |= BIT(3);
+ outb_p(reg, ASFDATABNKSEL);
+ } else {
+ reg |= BIT(2);
+ outb_p(reg, ASFDATABNKSEL);
+ }
+ }
+
+ outb_p(0, ASFSETDATARDPTR);
+ if (cmd & BIT(0))
+ return;
+
+ /*
+ * Although i2c_slave_event() returns an appropriate error code, we
+ * don't check it here because we're operating in the workqueue context.
+ */
+ i2c_slave_event(dev->target, I2C_SLAVE_WRITE_REQUESTED, &val);
+ for (idx = 0; idx < len; idx++) {
+ val = data[idx];
+ i2c_slave_event(dev->target, I2C_SLAVE_WRITE_RECEIVED, &val);
+ }
+ i2c_slave_event(dev->target, I2C_SLAVE_STOP, &val);
+}
+
+static void amd_asf_update_ioport_target(unsigned short piix4_smba, u8 bit,
+ unsigned long offset, bool set)
+{
+ unsigned long reg;
+
+ reg = inb_p(offset);
+ __assign_bit(bit, &reg, set);
+ outb_p(reg, offset);
+}
+
+static void amd_asf_update_mmio_target(struct amd_asf_dev *dev, u8 bit, bool set)
+{
+ unsigned long reg;
+
+ reg = ioread32(dev->mmio_cfg.addr);
+ __assign_bit(bit, &reg, set);
+ iowrite32(reg, dev->mmio_cfg.addr);
+}
+
+static void amd_asf_setup_target(struct amd_asf_dev *dev)
+{
+ unsigned short piix4_smba = dev->port_addr->start;
+
+ /* Reset both host and target before setting up */
+ outb_p(0, SMBHSTSTS);
+ outb_p(0, ASFSLVSTA);
+ outb_p(0, ASFSTA);
+
+ /* Update target address */
+ amd_asf_update_ioport_target(piix4_smba, ASF_SLV_LISTN, ASFLISADDR, true);
+ /* Enable target and set the clock */
+ amd_asf_update_mmio_target(dev, ASF_MSTR_EN, false);
+ amd_asf_update_mmio_target(dev, ASF_CLK_EN, true);
+ /* Enable target interrupt */
+ amd_asf_update_ioport_target(piix4_smba, ASF_SLV_INTR, ASFSLVEN, true);
+ amd_asf_update_ioport_target(piix4_smba, ASF_SLV_RST, ASFSLVEN, false);
+ /* Enable PEC and PEC append */
+ amd_asf_update_ioport_target(piix4_smba, ASF_DATA_EN, SMBHSTCNT, true);
+ amd_asf_update_ioport_target(piix4_smba, ASF_PEC_SP, SMBHSTCNT, true);
+}
+
+static int amd_asf_access(struct i2c_adapter *adap, u16 addr, u8 command, u8 *data)
+{
+ struct amd_asf_dev *dev = i2c_get_adapdata(adap);
+ unsigned short piix4_smba = dev->port_addr->start;
+ u8 i, len;
+
+ outb_p((addr << 1), SMBHSTADD);
+ outb_p(command, SMBHSTCMD);
+ len = data[0];
+ if (len == 0 || len > ASF_BLOCK_MAX_BYTES)
+ return -EINVAL;
+
+ outb_p(len, SMBHSTDAT0);
+ /* Reset SMBBLKDAT */
+ inb_p(SMBHSTCNT);
+ for (i = 1; i <= len; i++)
+ outb_p(data[i], SMBBLKDAT);
+
+ outb_p(PIIX4_BLOCK_DATA, SMBHSTCNT);
+ /* Enable PEC and PEC append */
+ amd_asf_update_ioport_target(piix4_smba, ASF_DATA_EN, SMBHSTCNT, true);
+ amd_asf_update_ioport_target(piix4_smba, ASF_PEC_SP, SMBHSTCNT, true);
+
+ return piix4_transaction(adap, piix4_smba);
+}
+
+static int amd_asf_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct amd_asf_dev *dev = i2c_get_adapdata(adap);
+ unsigned short piix4_smba = dev->port_addr->start;
+ u8 asf_data[ASF_BLOCK_MAX_BYTES];
+ struct i2c_msg *dev_msgs = msgs;
+ u8 prev_port;
+ int ret;
+
+ if (msgs->flags & I2C_M_RD) {
+ dev_err(&adap->dev, "ASF: Read not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ /* Exclude the receive header and PEC */
+ if (msgs->len > ASF_BLOCK_MAX_BYTES - 3) {
+ dev_warn(&adap->dev, "ASF: max message length exceeded\n");
+ return -EOPNOTSUPP;
+ }
+
+ asf_data[0] = dev_msgs->len;
+ memcpy(asf_data + 1, dev_msgs[0].buf, dev_msgs->len);
+
+ ret = piix4_sb800_region_request(&adap->dev, &dev->mmio_cfg);
+ if (ret)
+ return ret;
+
+ amd_asf_update_ioport_target(piix4_smba, ASF_SLV_RST, ASFSLVEN, true);
+ amd_asf_update_ioport_target(piix4_smba, ASF_SLV_LISTN, ASFLISADDR, false);
+ /* Clear ASF target status */
+ outb_p(0, ASFSLVSTA);
+
+ /* Enable ASF SMBus controller function */
+ amd_asf_update_mmio_target(dev, ASF_MSTR_EN, true);
+ prev_port = piix4_sb800_port_sel(0, &dev->mmio_cfg);
+ ret = amd_asf_access(adap, msgs->addr, msgs[0].buf[0], asf_data);
+ piix4_sb800_port_sel(prev_port, &dev->mmio_cfg);
+ amd_asf_setup_target(dev);
+ piix4_sb800_region_release(&adap->dev, &dev->mmio_cfg);
+ return ret;
+}
+
+static int amd_asf_reg_target(struct i2c_client *target)
+{
+ struct amd_asf_dev *dev = i2c_get_adapdata(target->adapter);
+ unsigned short piix4_smba = dev->port_addr->start;
+ int ret;
+ u8 reg;
+
+ if (dev->target)
+ return -EBUSY;
+
+ ret = piix4_sb800_region_request(&target->dev, &dev->mmio_cfg);
+ if (ret)
+ return ret;
+
+ reg = (target->addr << 1) | I2C_M_RD;
+ outb_p(reg, ASFLISADDR);
+
+ amd_asf_setup_target(dev);
+ dev->target = target;
+ amd_asf_update_ioport_target(piix4_smba, ASF_DATA_EN, ASFDATABNKSEL, false);
+ piix4_sb800_region_release(&target->dev, &dev->mmio_cfg);
+
+ return 0;
+}
+
+static int amd_asf_unreg_target(struct i2c_client *target)
+{
+ struct amd_asf_dev *dev = i2c_get_adapdata(target->adapter);
+ unsigned short piix4_smba = dev->port_addr->start;
+
+ amd_asf_update_ioport_target(piix4_smba, ASF_SLV_INTR, ASFSLVEN, false);
+ amd_asf_update_ioport_target(piix4_smba, ASF_SLV_RST, ASFSLVEN, true);
+ dev->target = NULL;
+
+ return 0;
+}
+
+static u32 amd_asf_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
+ I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_PEC | I2C_FUNC_SLAVE;
+}
+
+static const struct i2c_algorithm amd_asf_smbus_algorithm = {
+ .master_xfer = amd_asf_xfer,
+ .reg_slave = amd_asf_reg_target,
+ .unreg_slave = amd_asf_unreg_target,
+ .functionality = amd_asf_func,
+};
+
+static irqreturn_t amd_asf_irq_handler(int irq, void *ptr)
+{
+ struct amd_asf_dev *dev = ptr;
+ unsigned short piix4_smba = dev->port_addr->start;
+ u8 target_int = inb_p(ASFSTA);
+
+ if (target_int & BIT(6)) {
+ /* Target Interrupt */
+ outb_p(target_int | BIT(6), ASFSTA);
+ schedule_delayed_work(&dev->work_buf, HZ);
+ } else {
+ /* Controller Interrupt */
+ amd_asf_update_ioport_target(piix4_smba, ASF_SLV_INTR, SMBHSTSTS, true);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int amd_asf_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct amd_asf_dev *asf_dev;
+ struct resource *eoi_addr;
+ int ret, irq;
+
+ asf_dev = devm_kzalloc(dev, sizeof(*asf_dev), GFP_KERNEL);
+ if (!asf_dev)
+ return dev_err_probe(dev, -ENOMEM, "Failed to allocate memory\n");
+
+ asf_dev->mmio_cfg.use_mmio = true;
+ asf_dev->port_addr = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!asf_dev->port_addr)
+ return dev_err_probe(dev, -EINVAL, "missing IO resources\n");
+
+ /*
+ * The resource obtained via ACPI might not belong to the ASF device address space. Instead,
+ * it could be within other IP blocks of the ASIC, which are crucial for generating
+ * subsequent interrupts. Therefore, we avoid using devm_platform_ioremap_resource() and
+ * use platform_get_resource() and devm_ioremap() separately to prevent any address space
+ * conflicts.
+ */
+ eoi_addr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!eoi_addr)
+ return dev_err_probe(dev, -EINVAL, "missing MEM resources\n");
+
+ asf_dev->eoi_base = devm_ioremap(dev, eoi_addr->start, resource_size(eoi_addr));
+ if (!asf_dev->eoi_base)
+ return dev_err_probe(dev, -EBUSY, "failed mapping IO region\n");
+
+ ret = devm_delayed_work_autocancel(dev, &asf_dev->work_buf, amd_asf_process_target);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to create work queue\n");
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return dev_err_probe(dev, irq, "missing IRQ resources\n");
+
+ ret = devm_request_irq(dev, irq, amd_asf_irq_handler, IRQF_SHARED, "amd_asf", asf_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to request irq: %d for use\n", irq);
+
+ asf_dev->adap.owner = THIS_MODULE;
+ asf_dev->adap.algo = &amd_asf_smbus_algorithm;
+ asf_dev->adap.dev.parent = dev;
+
+ i2c_set_adapdata(&asf_dev->adap, asf_dev);
+ snprintf(asf_dev->adap.name, sizeof(asf_dev->adap.name), "AMD ASF adapter");
+
+ return devm_i2c_add_adapter(dev, &asf_dev->adap);
+}
+
+static const struct acpi_device_id amd_asf_acpi_ids[] = {
+ { "AMDI001A" },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, amd_asf_acpi_ids);
+
+static struct platform_driver amd_asf_driver = {
+ .driver = {
+ .name = "i2c-amd-asf",
+ .acpi_match_table = amd_asf_acpi_ids,
+ },
+ .probe = amd_asf_probe,
+};
+module_platform_driver(amd_asf_driver);
+
+MODULE_IMPORT_NS(PIIX4_SMBUS);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AMD Alert Standard Format Driver");