diff options
author | Linus Torvalds | 2024-09-24 12:53:54 -0700 |
---|---|---|
committer | Linus Torvalds | 2024-09-24 12:53:54 -0700 |
commit | cd3d6477298155482b772eae481ce01c9e764129 (patch) | |
tree | 3805e45328f4f496040ddec164a788b0ccd981b6 /drivers | |
parent | ba0c0cb56f227d9af9c19a276fac982c492c079f (diff) | |
parent | 61850725779709369c7e907ae8c7c75dc7cec4f3 (diff) |
Merge tag 'i3c/for-6.12' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux
Pull i3c updates from Alexandre Belloni:
"This adds support for the I3C HCI controller of the AMD SoC which as
expected requires quirks. Also fixes for the other drivers, including
rate selection fixes for svc.
Core:
- allow adjusting first broadcast address speed
Drivers:
- cdns: few fixes
- mipi-i3c-hci: Add AMD SoC I3C controller support and quirks, fix
get_i3c_mode
- svc: adjust rates, fix race condition"
* tag 'i3c/for-6.12' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux:
i3c: master: svc: Fix use after free vulnerability in svc_i3c_master Driver Due to Race Condition
i3c: master: cdns: Fix use after free vulnerability in cdns_i3c_master Driver Due to Race Condition
i3c: master: svc: adjust SDR according to i3c spec
i3c: master: svc: use slow speed for first broadcast address
i3c: master: support to adjust first broadcast address speed
i3c/master: cmd_v1: Fix the rule for getting i3c mode
i3c: master: cdns: fix module autoloading
i3c: mipi-i3c-hci: Add a quirk to set Response buffer threshold
i3c: mipi-i3c-hci: Add a quirk to set timing parameters
i3c: mipi-i3c-hci: Relocate helper macros to HCI header file
i3c: mipi-i3c-hci: Add a quirk to set PIO mode
i3c: mipi-i3c-hci: Read HC_CONTROL_PIO_MODE only after i3c hci v1.1
i3c: mipi-i3c-hci: Add AMDI5017 ACPI ID to the I3C Support List
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/i3c/master.c | 12 | ||||
-rw-r--r-- | drivers/i3c/master/i3c-master-cdns.c | 2 | ||||
-rw-r--r-- | drivers/i3c/master/mipi-i3c-hci/Makefile | 3 | ||||
-rw-r--r-- | drivers/i3c/master/mipi-i3c-hci/cmd_v1.c | 12 | ||||
-rw-r--r-- | drivers/i3c/master/mipi-i3c-hci/core.c | 36 | ||||
-rw-r--r-- | drivers/i3c/master/mipi-i3c-hci/hci.h | 10 | ||||
-rw-r--r-- | drivers/i3c/master/mipi-i3c-hci/hci_quirks.c | 44 | ||||
-rw-r--r-- | drivers/i3c/master/svc-i3c-master.c | 84 |
8 files changed, 177 insertions, 26 deletions
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 7028f03c2c42..6f3eb710a75d 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1868,6 +1868,12 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) goto err_bus_cleanup; } + if (master->ops->set_speed) { + ret = master->ops->set_speed(master, I3C_OPEN_DRAIN_SLOW_SPEED); + if (ret) + goto err_bus_cleanup; + } + /* * Reset all dynamic address that may have been assigned before * (assigned by the bootloader for example). @@ -1876,6 +1882,12 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) if (ret && ret != I3C_ERROR_M2) goto err_bus_cleanup; + if (master->ops->set_speed) { + master->ops->set_speed(master, I3C_OPEN_DRAIN_NORMAL_SPEED); + if (ret) + goto err_bus_cleanup; + } + /* Disable all slave events before starting DAA. */ ret = i3c_master_disec_locked(master, I3C_BROADCAST_ADDR, I3C_CCC_EVENT_SIR | I3C_CCC_EVENT_MR | diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c index c1627f3552ce..fe4d59833ad5 100644 --- a/drivers/i3c/master/i3c-master-cdns.c +++ b/drivers/i3c/master/i3c-master-cdns.c @@ -1562,6 +1562,7 @@ static const struct of_device_id cdns_i3c_master_of_ids[] = { { .compatible = "cdns,i3c-master", .data = &cdns_i3c_devdata }, { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, cdns_i3c_master_of_ids); static int cdns_i3c_master_probe(struct platform_device *pdev) { @@ -1666,6 +1667,7 @@ static void cdns_i3c_master_remove(struct platform_device *pdev) { struct cdns_i3c_master *master = platform_get_drvdata(pdev); + cancel_work_sync(&master->hj_work); i3c_master_unregister(&master->base); clk_disable_unprepare(master->sysclk); diff --git a/drivers/i3c/master/mipi-i3c-hci/Makefile b/drivers/i3c/master/mipi-i3c-hci/Makefile index a658e7b8262c..1f8cd5c48fde 100644 --- a/drivers/i3c/master/mipi-i3c-hci/Makefile +++ b/drivers/i3c/master/mipi-i3c-hci/Makefile @@ -3,4 +3,5 @@ obj-$(CONFIG_MIPI_I3C_HCI) += mipi-i3c-hci.o mipi-i3c-hci-y := core.o ext_caps.o pio.o dma.o \ cmd_v1.o cmd_v2.o \ - dat_v1.o dct_v1.o + dat_v1.o dct_v1.o \ + hci_quirks.o diff --git a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c index 638b054d6c92..dd636094b07f 100644 --- a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c +++ b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c @@ -123,17 +123,15 @@ static enum hci_cmd_mode get_i3c_mode(struct i3c_hci *hci) { struct i3c_bus *bus = i3c_master_get_bus(&hci->master); - if (bus->scl_rate.i3c >= 12500000) - return MODE_I3C_SDR0; if (bus->scl_rate.i3c > 8000000) - return MODE_I3C_SDR1; + return MODE_I3C_SDR0; if (bus->scl_rate.i3c > 6000000) - return MODE_I3C_SDR2; + return MODE_I3C_SDR1; if (bus->scl_rate.i3c > 4000000) - return MODE_I3C_SDR3; + return MODE_I3C_SDR2; if (bus->scl_rate.i3c > 2000000) - return MODE_I3C_SDR4; - return MODE_I3C_Fm_FmP; + return MODE_I3C_SDR3; + return MODE_I3C_SDR4; } static enum hci_cmd_mode get_i2c_mode(struct i3c_hci *hci) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 4e7d6a43ee9b..a82c47c9986d 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -12,7 +12,6 @@ #include <linux/errno.h> #include <linux/i3c/master.h> #include <linux/interrupt.h> -#include <linux/io.h> #include <linux/iopoll.h> #include <linux/module.h> #include <linux/platform_device.h> @@ -27,11 +26,6 @@ * Host Controller Capabilities and Operation Registers */ -#define reg_read(r) readl(hci->base_regs + (r)) -#define reg_write(r, v) writel(v, hci->base_regs + (r)) -#define reg_set(r, v) reg_write(r, reg_read(r) | (v)) -#define reg_clear(r, v) reg_write(r, reg_read(r) & ~(v)) - #define HCI_VERSION 0x00 /* HCI Version (in BCD) */ #define HC_CONTROL 0x04 @@ -152,6 +146,10 @@ static int i3c_hci_bus_init(struct i3c_master_controller *m) if (ret) return ret; + /* Set RESP_BUF_THLD to 0(n) to get 1(n+1) response */ + if (hci->quirks & HCI_QUIRK_RESP_BUF_THLD) + amd_set_resp_buf_thld(hci); + reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE); DBG("HC_CONTROL = %#x", reg_read(HC_CONTROL)); @@ -630,8 +628,8 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id) static int i3c_hci_init(struct i3c_hci *hci) { + bool size_in_dwords, mode_selector; u32 regval, offset; - bool size_in_dwords; int ret; /* Validate HCI hardware version */ @@ -753,10 +751,17 @@ static int i3c_hci_init(struct i3c_hci *hci) return -EINVAL; } + mode_selector = hci->version_major > 1 || + (hci->version_major == 1 && hci->version_minor > 0); + + /* Quirk for HCI_QUIRK_PIO_MODE on AMD platforms */ + if (hci->quirks & HCI_QUIRK_PIO_MODE) + hci->RHS_regs = NULL; + /* Try activating DMA operations first */ if (hci->RHS_regs) { reg_clear(HC_CONTROL, HC_CONTROL_PIO_MODE); - if (reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE) { + if (mode_selector && (reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE)) { dev_err(&hci->master.dev, "PIO mode is stuck\n"); ret = -EIO; } else { @@ -768,7 +773,7 @@ static int i3c_hci_init(struct i3c_hci *hci) /* If no DMA, try PIO */ if (!hci->io && hci->PIO_regs) { reg_set(HC_CONTROL, HC_CONTROL_PIO_MODE); - if (!(reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE)) { + if (mode_selector && !(reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE)) { dev_err(&hci->master.dev, "DMA mode is stuck\n"); ret = -EIO; } else { @@ -784,6 +789,10 @@ static int i3c_hci_init(struct i3c_hci *hci) return ret; } + /* Configure OD and PP timings for AMD platforms */ + if (hci->quirks & HCI_QUIRK_OD_PP_TIMING) + amd_set_od_pp_timing(hci); + return 0; } @@ -803,6 +812,8 @@ static int i3c_hci_probe(struct platform_device *pdev) /* temporary for dev_printk's, to be replaced in i3c_master_register */ hci->master.dev.init_name = dev_name(&pdev->dev); + hci->quirks = (unsigned long)device_get_match_data(&pdev->dev); + ret = i3c_hci_init(hci); if (ret) return ret; @@ -834,12 +845,19 @@ static const __maybe_unused struct of_device_id i3c_hci_of_match[] = { }; MODULE_DEVICE_TABLE(of, i3c_hci_of_match); +static const struct acpi_device_id i3c_hci_acpi_match[] = { + { "AMDI5017", HCI_QUIRK_PIO_MODE | HCI_QUIRK_OD_PP_TIMING | HCI_QUIRK_RESP_BUF_THLD }, + {} +}; +MODULE_DEVICE_TABLE(acpi, i3c_hci_acpi_match); + static struct platform_driver i3c_hci_driver = { .probe = i3c_hci_probe, .remove_new = i3c_hci_remove, .driver = { .name = "mipi-i3c-hci", .of_match_table = of_match_ptr(i3c_hci_of_match), + .acpi_match_table = i3c_hci_acpi_match, }, }; module_platform_driver(i3c_hci_driver); diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index f94d95e024be..aaa47ac47381 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -10,6 +10,7 @@ #ifndef HCI_H #define HCI_H +#include <linux/io.h> /* Handy logging macro to save on line length */ #define DBG(x, ...) pr_devel("%s: " x "\n", __func__, ##__VA_ARGS__) @@ -26,6 +27,10 @@ #define W2_BIT_(x) BIT((x) - 64) #define W3_BIT_(x) BIT((x) - 96) +#define reg_read(r) readl(hci->base_regs + (r)) +#define reg_write(r, v) writel(v, hci->base_regs + (r)) +#define reg_set(r, v) reg_write(r, reg_read(r) | (v)) +#define reg_clear(r, v) reg_write(r, reg_read(r) & ~(v)) struct hci_cmd_ops; @@ -135,11 +140,16 @@ struct i3c_hci_dev_data { /* list of quirks */ #define HCI_QUIRK_RAW_CCC BIT(1) /* CCC framing must be explicit */ +#define HCI_QUIRK_PIO_MODE BIT(2) /* Set PIO mode for AMD platforms */ +#define HCI_QUIRK_OD_PP_TIMING BIT(3) /* Set OD and PP timings for AMD platforms */ +#define HCI_QUIRK_RESP_BUF_THLD BIT(4) /* Set resp buf thld to 0 for AMD platforms */ /* global functions */ void mipi_i3c_hci_resume(struct i3c_hci *hci); void mipi_i3c_hci_pio_reset(struct i3c_hci *hci); void mipi_i3c_hci_dct_index_reset(struct i3c_hci *hci); +void amd_set_od_pp_timing(struct i3c_hci *hci); +void amd_set_resp_buf_thld(struct i3c_hci *hci); #endif diff --git a/drivers/i3c/master/mipi-i3c-hci/hci_quirks.c b/drivers/i3c/master/mipi-i3c-hci/hci_quirks.c new file mode 100644 index 000000000000..3b9c6e76c536 --- /dev/null +++ b/drivers/i3c/master/mipi-i3c-hci/hci_quirks.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * I3C HCI Quirks + * + * Copyright 2024 Advanced Micro Devices, Inc. + * + * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> + * Guruvendra Punugupati <Guruvendra.Punugupati@amd.com> + */ + +#include <linux/i3c/master.h> +#include "hci.h" + +/* Timing registers */ +#define HCI_SCL_I3C_OD_TIMING 0x214 +#define HCI_SCL_I3C_PP_TIMING 0x218 +#define HCI_SDA_HOLD_SWITCH_DLY_TIMING 0x230 + +/* Timing values to configure 9MHz frequency */ +#define AMD_SCL_I3C_OD_TIMING 0x00cf00cf +#define AMD_SCL_I3C_PP_TIMING 0x00160016 + +#define QUEUE_THLD_CTRL 0xD0 + +void amd_set_od_pp_timing(struct i3c_hci *hci) +{ + u32 data; + + reg_write(HCI_SCL_I3C_OD_TIMING, AMD_SCL_I3C_OD_TIMING); + reg_write(HCI_SCL_I3C_PP_TIMING, AMD_SCL_I3C_PP_TIMING); + data = reg_read(HCI_SDA_HOLD_SWITCH_DLY_TIMING); + /* Configure maximum TX hold time */ + data |= W0_MASK(18, 16); + reg_write(HCI_SDA_HOLD_SWITCH_DLY_TIMING, data); +} + +void amd_set_resp_buf_thld(struct i3c_hci *hci) +{ + u32 data; + + data = reg_read(QUEUE_THLD_CTRL); + data = data & ~W0_MASK(15, 8); + reg_write(QUEUE_THLD_CTRL, data); +} diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 0a68fd1b81d4..a7bfc678153e 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -127,6 +127,8 @@ /* This parameter depends on the implementation and may be tuned */ #define SVC_I3C_FIFO_SIZE 16 +#define SVC_I3C_PPBAUD_MAX 15 +#define SVC_I3C_QUICK_I2C_CLK 4170000 #define SVC_I3C_EVENT_IBI BIT(0) #define SVC_I3C_EVENT_HOTJOIN BIT(1) @@ -182,6 +184,7 @@ struct svc_i3c_regs_save { * @ibi.lock: IBI lock * @lock: Transfer lock, protect between IBI work thread and callbacks from master * @enabled_events: Bit masks for enable events (IBI, HotJoin). + * @mctrl_config: Configuration value in SVC_I3C_MCTRL for setting speed back. */ struct svc_i3c_master { struct i3c_master_controller base; @@ -212,6 +215,7 @@ struct svc_i3c_master { } ibi; struct mutex lock; int enabled_events; + u32 mctrl_config; }; /** @@ -529,12 +533,61 @@ static irqreturn_t svc_i3c_master_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +static int svc_i3c_master_set_speed(struct i3c_master_controller *m, + enum i3c_open_drain_speed speed) +{ + struct svc_i3c_master *master = to_svc_i3c_master(m); + struct i3c_bus *bus = i3c_master_get_bus(&master->base); + u32 ppbaud, odbaud, odhpp, mconfig; + unsigned long fclk_rate; + int ret; + + ret = pm_runtime_resume_and_get(master->dev); + if (ret < 0) { + dev_err(master->dev, "<%s> Cannot get runtime PM.\n", __func__); + return ret; + } + + switch (speed) { + case I3C_OPEN_DRAIN_SLOW_SPEED: + fclk_rate = clk_get_rate(master->fclk); + if (!fclk_rate) { + ret = -EINVAL; + goto rpm_out; + } + /* + * Set 50% duty-cycle I2C speed to I3C OPEN-DRAIN mode, so the first + * broadcast address is visible to all I2C/I3C devices on the I3C bus. + * I3C device working as a I2C device will turn off its 50ns Spike + * Filter to change to I3C mode. + */ + mconfig = master->mctrl_config; + ppbaud = FIELD_GET(GENMASK(11, 8), mconfig); + odhpp = 0; + odbaud = DIV_ROUND_UP(fclk_rate, bus->scl_rate.i2c * (2 + 2 * ppbaud)) - 1; + mconfig &= ~GENMASK(24, 16); + mconfig |= SVC_I3C_MCONFIG_ODBAUD(odbaud) | SVC_I3C_MCONFIG_ODHPP(odhpp); + writel(mconfig, master->regs + SVC_I3C_MCONFIG); + break; + case I3C_OPEN_DRAIN_NORMAL_SPEED: + writel(master->mctrl_config, master->regs + SVC_I3C_MCONFIG); + break; + } + +rpm_out: + pm_runtime_mark_last_busy(master->dev); + pm_runtime_put_autosuspend(master->dev); + + return ret; +} + static int svc_i3c_master_bus_init(struct i3c_master_controller *m) { struct svc_i3c_master *master = to_svc_i3c_master(m); struct i3c_bus *bus = i3c_master_get_bus(m); struct i3c_device_info info = {}; unsigned long fclk_rate, fclk_period_ns; + unsigned long i2c_period_ns, i2c_scl_rate, i3c_scl_rate; unsigned int high_period_ns, od_low_period_ns; u32 ppbaud, pplow, odhpp, odbaud, odstop, i2cbaud, reg; int ret; @@ -555,12 +608,15 @@ static int svc_i3c_master_bus_init(struct i3c_master_controller *m) } fclk_period_ns = DIV_ROUND_UP(1000000000, fclk_rate); + i2c_period_ns = DIV_ROUND_UP(1000000000, bus->scl_rate.i2c); + i2c_scl_rate = bus->scl_rate.i2c; + i3c_scl_rate = bus->scl_rate.i3c; /* * Using I3C Push-Pull mode, target is 12.5MHz/80ns period. * Simplest configuration is using a 50% duty-cycle of 40ns. */ - ppbaud = DIV_ROUND_UP(40, fclk_period_ns) - 1; + ppbaud = DIV_ROUND_UP(fclk_rate / 2, i3c_scl_rate) - 1; pplow = 0; /* @@ -570,7 +626,7 @@ static int svc_i3c_master_bus_init(struct i3c_master_controller *m) */ odhpp = 1; high_period_ns = (ppbaud + 1) * fclk_period_ns; - odbaud = DIV_ROUND_UP(240 - high_period_ns, high_period_ns) - 1; + odbaud = DIV_ROUND_UP(fclk_rate, SVC_I3C_QUICK_I2C_CLK * (1 + ppbaud)) - 2; od_low_period_ns = (odbaud + 1) * high_period_ns; switch (bus->mode) { @@ -579,20 +635,27 @@ static int svc_i3c_master_bus_init(struct i3c_master_controller *m) odstop = 0; break; case I3C_BUS_MODE_MIXED_FAST: - case I3C_BUS_MODE_MIXED_LIMITED: /* * Using I2C Fm+ mode, target is 1MHz/1000ns, the difference * between the high and low period does not really matter. */ - i2cbaud = DIV_ROUND_UP(1000, od_low_period_ns) - 2; + i2cbaud = DIV_ROUND_UP(i2c_period_ns, od_low_period_ns) - 2; odstop = 1; break; + case I3C_BUS_MODE_MIXED_LIMITED: case I3C_BUS_MODE_MIXED_SLOW: - /* - * Using I2C Fm mode, target is 0.4MHz/2500ns, with the same - * constraints as the FM+ mode. - */ - i2cbaud = DIV_ROUND_UP(2500, od_low_period_ns) - 2; + /* I3C PP + I3C OP + I2C OP both use i2c clk rate */ + if (ppbaud > SVC_I3C_PPBAUD_MAX) { + ppbaud = SVC_I3C_PPBAUD_MAX; + pplow = DIV_ROUND_UP(fclk_rate, i3c_scl_rate) - (2 + 2 * ppbaud); + } + + high_period_ns = (ppbaud + 1) * fclk_period_ns; + odhpp = 0; + odbaud = DIV_ROUND_UP(fclk_rate, i2c_scl_rate * (2 + 2 * ppbaud)) - 1; + + od_low_period_ns = (odbaud + 1) * high_period_ns; + i2cbaud = DIV_ROUND_UP(i2c_period_ns, od_low_period_ns) - 2; odstop = 1; break; default: @@ -611,6 +674,7 @@ static int svc_i3c_master_bus_init(struct i3c_master_controller *m) SVC_I3C_MCONFIG_I2CBAUD(i2cbaud); writel(reg, master->regs + SVC_I3C_MCONFIG); + master->mctrl_config = reg; /* Master core's registration */ ret = i3c_master_get_free_addr(m, 0); if (ret < 0) @@ -1645,6 +1709,7 @@ static const struct i3c_master_controller_ops svc_i3c_master_ops = { .disable_ibi = svc_i3c_master_disable_ibi, .enable_hotjoin = svc_i3c_master_enable_hotjoin, .disable_hotjoin = svc_i3c_master_disable_hotjoin, + .set_speed = svc_i3c_master_set_speed, }; static int svc_i3c_master_prepare_clks(struct svc_i3c_master *master) @@ -1775,6 +1840,7 @@ static void svc_i3c_master_remove(struct platform_device *pdev) { struct svc_i3c_master *master = platform_get_drvdata(pdev); + cancel_work_sync(&master->hj_work); i3c_master_unregister(&master->base); pm_runtime_dont_use_autosuspend(&pdev->dev); |