mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 21:21:41 +00:00
I3C for 6.11
Drivers: - dw: optional apb clock and power management support, IBI handling fixes - mipi-i3c-hci: IBI handling fixes - svc: few fixes -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEBqsFVZXh8s/0O5JiY6TcMGxwOjIFAmajlukACgkQY6TcMGxw OjJ4iw//db7P6x0wmy5xkIRktjCY3rqDyBHvcuoVYuFFQqTyRcu7RybRL8HTiem7 tE+dd9d2GyMaq2aG3P6lzwRozVUUmTnKECP3IGcPTw70n3y2xQEYC/Y/CCmnSNy4 hY3m/e9Z0NT7g98rOz6iMJrw0O3TZZVv+va88EK2MvtU76xf2e8y0CZeOsA1c5SZ mGnICFtluV2i4Yj+zj6I5e8Dbn+rqpIz3MWtdLzJK9/q6BIZjBtoRiDFXlP5twTf 4PjqCI0oq/oZmhq6zEPE7eRjr0xYZC7XLDpQPnDXrpJqsXvosW5tDPxHsx0rE3RY 6q5RwJdSKwKrTT5Ttp3KYAnMjaxTScYoXuMx5f5JpdxjeJ0iTNi7cmoobqw5uJIh 0RPBxPYmQlW/eHcvY/7Se+b4VwGM2upIhJRU+1Y0hRJYcF/qXA/KR2jQz0fIt7O2 agWjcc3SW429YXfYfy7SSRLT790n913bypUoO6eUE8Hde9oyqSLAKWbrZsTj/nBx PhRszqmlVTWvVWxHIhy6fLS+NIDefWexL7zszQg9ME4AdafUBDOoabDSn1IjN1WC +/YPwkv9Q9gQC43vg/6Lu7r3IghsWLdRt66cSRok3KGfvQbPIUwbzgNAjHOace0h DS37RtB4LXVWMWJ2WbE3N6N9qmlsFAstimdoffUCGbb4cuWxnwU= =WFhg -----END PGP SIGNATURE----- Merge tag 'i3c/for-6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux Pull i3c updates from Alexandre Belloni: "This cycle, there are new features for the Designware controller and fixes for the other IPs: - dw: optional apb clock and power management support, IBI handling fixes - mipi-i3c-hci: IBI handling fixes - svc: a few fixes" * tag 'i3c/for-6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: dt-bindings: i3c: add header for generic I3C flags i3c: master: svc: Fix error code in svc_i3c_master_do_daa_locked() i3c: master: Enhance i3c_bus_type visibility for device searching & event monitoring i3c: dw: Add power management support i3c: dw: Add some functions for reusability i3c: dw: Save timing registers and other values i3c: master: svc: Improve DAA STOP handle code logic i3c: dw: Add optional apb clock i3c: dw: Use new *_enabled clk API dt-bindings: i3c: dw: Add apb clock binding i3c: master: svc: Convert comma to semicolon i3c: mipi-i3c-hci: Round IBI data chunk size to HW supported value i3c: mipi-i3c-hci: Error out instead on BUG_ON() in IBI DMA setup i3c: mipi-i3c-hci: Set IBI Status and Data Ring base addresses i3c: mipi-i3c-hci: Switch to lower_32_bits()/upper_32_bits() helpers i3c: dw: Remove ibi_capable property i3c: dw: Fix IBI intr programming i3c: dw: Fix clearing queue thld i3c: mipi-i3c-hci: Fix number of DAT/DCT entries for HCI versions < 1.1 i3c: master: svc: resend target address when get NACK
This commit is contained in:
commit
c85e1497dd
@ -91,6 +91,7 @@ patternProperties:
|
||||
- const: 0
|
||||
- description: |
|
||||
Shall encode the I3C LVR (Legacy Virtual Register):
|
||||
See include/dt-bindings/i3c/i3c.h
|
||||
bit[31:8]: unused/ignored
|
||||
bit[7:5]: I2C device index. Possible values:
|
||||
* 0: I2C device has a 50 ns spike filter
|
||||
@ -153,6 +154,8 @@ additionalProperties: true
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/i3c/i3c.h>
|
||||
|
||||
i3c@d040000 {
|
||||
compatible = "cdns,i3c-master";
|
||||
clocks = <&coreclock>, <&i3csysclock>;
|
||||
@ -166,7 +169,7 @@ examples:
|
||||
/* I2C device. */
|
||||
eeprom@57 {
|
||||
compatible = "atmel,24c01";
|
||||
reg = <0x57 0x0 0x10>;
|
||||
reg = <0x57 0x0 (I2C_FM | I2C_FILTER)>;
|
||||
pagesize = <0x8>;
|
||||
};
|
||||
|
||||
|
@ -20,7 +20,16 @@ properties:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
minItems: 1
|
||||
items:
|
||||
- description: Core clock
|
||||
- description: APB clock
|
||||
|
||||
clock-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: core
|
||||
- const: apb
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
@ -10655,6 +10655,7 @@ F: Documentation/ABI/testing/sysfs-bus-i3c
|
||||
F: Documentation/devicetree/bindings/i3c/
|
||||
F: Documentation/driver-api/i3c
|
||||
F: drivers/i3c/
|
||||
F: include/dt-bindings/i3c/
|
||||
F: include/linux/i3c/
|
||||
|
||||
IBM Operation Panel Input Driver
|
||||
|
@ -10,8 +10,6 @@
|
||||
|
||||
#include <linux/i3c/master.h>
|
||||
|
||||
extern const struct bus_type i3c_bus_type;
|
||||
|
||||
void i3c_bus_normaluse_lock(struct i3c_bus *bus);
|
||||
void i3c_bus_normaluse_unlock(struct i3c_bus *bus);
|
||||
|
||||
|
@ -342,6 +342,7 @@ const struct bus_type i3c_bus_type = {
|
||||
.probe = i3c_device_probe,
|
||||
.remove = i3c_device_remove,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(i3c_bus_type);
|
||||
|
||||
static enum i3c_addr_slot_status
|
||||
i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr)
|
||||
|
@ -156,7 +156,6 @@ static int ast2600_i3c_probe(struct platform_device *pdev)
|
||||
i3c->sda_pullup);
|
||||
|
||||
i3c->dw.platform_ops = &ast2600_i3c_ops;
|
||||
i3c->dw.ibi_capable = true;
|
||||
return dw_i3c_common_probe(&i3c->dw, pdev);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,9 @@
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
@ -217,7 +219,7 @@
|
||||
#define I3C_BUS_THIGH_MAX_NS 41
|
||||
|
||||
#define XFER_TIMEOUT (msecs_to_jiffies(1000))
|
||||
|
||||
#define RPM_AUTOSUSPEND_TIMEOUT 1000 /* ms */
|
||||
struct dw_i3c_cmd {
|
||||
u32 cmd_lo;
|
||||
u32 cmd_hi;
|
||||
@ -300,7 +302,14 @@ static void dw_i3c_master_disable(struct dw_i3c_master *master)
|
||||
|
||||
static void dw_i3c_master_enable(struct dw_i3c_master *master)
|
||||
{
|
||||
writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_ENABLE,
|
||||
u32 dev_ctrl;
|
||||
|
||||
dev_ctrl = readl(master->regs + DEVICE_CTRL);
|
||||
/* For now don't support Hot-Join */
|
||||
dev_ctrl |= DEV_CTRL_HOT_JOIN_NACK;
|
||||
if (master->i2c_slv_prsnt)
|
||||
dev_ctrl |= DEV_CTRL_I2C_SLAVE_PRESENT;
|
||||
writel(dev_ctrl | DEV_CTRL_ENABLE,
|
||||
master->regs + DEVICE_CTRL);
|
||||
}
|
||||
|
||||
@ -521,6 +530,32 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
|
||||
dw_i3c_master_start_xfer_locked(master);
|
||||
}
|
||||
|
||||
static void dw_i3c_master_set_intr_regs(struct dw_i3c_master *master)
|
||||
{
|
||||
u32 thld_ctrl;
|
||||
|
||||
thld_ctrl = readl(master->regs + QUEUE_THLD_CTRL);
|
||||
thld_ctrl &= ~(QUEUE_THLD_CTRL_RESP_BUF_MASK |
|
||||
QUEUE_THLD_CTRL_IBI_STAT_MASK |
|
||||
QUEUE_THLD_CTRL_IBI_DATA_MASK);
|
||||
thld_ctrl |= QUEUE_THLD_CTRL_IBI_STAT(1) |
|
||||
QUEUE_THLD_CTRL_IBI_DATA(31);
|
||||
writel(thld_ctrl, master->regs + QUEUE_THLD_CTRL);
|
||||
|
||||
thld_ctrl = readl(master->regs + DATA_BUFFER_THLD_CTRL);
|
||||
thld_ctrl &= ~DATA_BUFFER_THLD_CTRL_RX_BUF;
|
||||
writel(thld_ctrl, master->regs + DATA_BUFFER_THLD_CTRL);
|
||||
|
||||
writel(INTR_ALL, master->regs + INTR_STATUS);
|
||||
writel(INTR_MASTER_MASK, master->regs + INTR_STATUS_EN);
|
||||
writel(INTR_MASTER_MASK, master->regs + INTR_SIGNAL_EN);
|
||||
|
||||
master->sir_rej_mask = IBI_REQ_REJECT_ALL;
|
||||
writel(master->sir_rej_mask, master->regs + IBI_SIR_REQ_REJECT);
|
||||
|
||||
writel(IBI_REQ_REJECT_ALL, master->regs + IBI_MR_REQ_REJECT);
|
||||
}
|
||||
|
||||
static int dw_i3c_clk_cfg(struct dw_i3c_master *master)
|
||||
{
|
||||
unsigned long core_rate, core_period;
|
||||
@ -543,18 +578,22 @@ static int dw_i3c_clk_cfg(struct dw_i3c_master *master)
|
||||
|
||||
scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
|
||||
writel(scl_timing, master->regs + SCL_I3C_PP_TIMING);
|
||||
master->i3c_pp_timing = scl_timing;
|
||||
|
||||
/*
|
||||
* In pure i3c mode, MST_FREE represents tCAS. In shared mode, this
|
||||
* will be set up by dw_i2c_clk_cfg as tLOW.
|
||||
*/
|
||||
if (master->base.bus.mode == I3C_BUS_MODE_PURE)
|
||||
if (master->base.bus.mode == I3C_BUS_MODE_PURE) {
|
||||
writel(BUS_I3C_MST_FREE(lcnt), master->regs + BUS_FREE_TIMING);
|
||||
master->bus_free_timing = BUS_I3C_MST_FREE(lcnt);
|
||||
}
|
||||
|
||||
lcnt = max_t(u8,
|
||||
DIV_ROUND_UP(I3C_BUS_TLOW_OD_MIN_NS, core_period), lcnt);
|
||||
scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
|
||||
writel(scl_timing, master->regs + SCL_I3C_OD_TIMING);
|
||||
master->i3c_od_timing = scl_timing;
|
||||
|
||||
lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_SDR1_SCL_RATE) - hcnt;
|
||||
scl_timing = SCL_EXT_LCNT_1(lcnt);
|
||||
@ -565,6 +604,7 @@ static int dw_i3c_clk_cfg(struct dw_i3c_master *master)
|
||||
lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_SDR4_SCL_RATE) - hcnt;
|
||||
scl_timing |= SCL_EXT_LCNT_4(lcnt);
|
||||
writel(scl_timing, master->regs + SCL_EXT_LCNT_TIMING);
|
||||
master->ext_lcnt_timing = scl_timing;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -586,16 +626,21 @@ static int dw_i2c_clk_cfg(struct dw_i3c_master *master)
|
||||
scl_timing = SCL_I2C_FMP_TIMING_HCNT(hcnt) |
|
||||
SCL_I2C_FMP_TIMING_LCNT(lcnt);
|
||||
writel(scl_timing, master->regs + SCL_I2C_FMP_TIMING);
|
||||
master->i2c_fmp_timing = scl_timing;
|
||||
|
||||
lcnt = DIV_ROUND_UP(I3C_BUS_I2C_FM_TLOW_MIN_NS, core_period);
|
||||
hcnt = DIV_ROUND_UP(core_rate, I3C_BUS_I2C_FM_SCL_RATE) - lcnt;
|
||||
scl_timing = SCL_I2C_FM_TIMING_HCNT(hcnt) |
|
||||
SCL_I2C_FM_TIMING_LCNT(lcnt);
|
||||
writel(scl_timing, master->regs + SCL_I2C_FM_TIMING);
|
||||
master->i2c_fm_timing = scl_timing;
|
||||
|
||||
writel(BUS_I3C_MST_FREE(lcnt), master->regs + BUS_FREE_TIMING);
|
||||
master->bus_free_timing = BUS_I3C_MST_FREE(lcnt);
|
||||
|
||||
writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_I2C_SLAVE_PRESENT,
|
||||
master->regs + DEVICE_CTRL);
|
||||
master->i2c_slv_prsnt = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -605,69 +650,58 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
|
||||
struct dw_i3c_master *master = to_dw_i3c_master(m);
|
||||
struct i3c_bus *bus = i3c_master_get_bus(m);
|
||||
struct i3c_device_info info = { };
|
||||
u32 thld_ctrl;
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_resume_and_get(master->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(master->dev,
|
||||
"<%s> cannot resume i3c bus master, err: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = master->platform_ops->init(master);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto rpm_out;
|
||||
|
||||
switch (bus->mode) {
|
||||
case I3C_BUS_MODE_MIXED_FAST:
|
||||
case I3C_BUS_MODE_MIXED_LIMITED:
|
||||
ret = dw_i2c_clk_cfg(master);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto rpm_out;
|
||||
fallthrough;
|
||||
case I3C_BUS_MODE_PURE:
|
||||
ret = dw_i3c_clk_cfg(master);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto rpm_out;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto rpm_out;
|
||||
}
|
||||
|
||||
thld_ctrl = readl(master->regs + QUEUE_THLD_CTRL);
|
||||
thld_ctrl &= ~(QUEUE_THLD_CTRL_RESP_BUF_MASK |
|
||||
QUEUE_THLD_CTRL_IBI_STAT_MASK |
|
||||
QUEUE_THLD_CTRL_IBI_STAT_MASK);
|
||||
thld_ctrl |= QUEUE_THLD_CTRL_IBI_STAT(1) |
|
||||
QUEUE_THLD_CTRL_IBI_DATA(31);
|
||||
writel(thld_ctrl, master->regs + QUEUE_THLD_CTRL);
|
||||
|
||||
thld_ctrl = readl(master->regs + DATA_BUFFER_THLD_CTRL);
|
||||
thld_ctrl &= ~DATA_BUFFER_THLD_CTRL_RX_BUF;
|
||||
writel(thld_ctrl, master->regs + DATA_BUFFER_THLD_CTRL);
|
||||
|
||||
writel(INTR_ALL, master->regs + INTR_STATUS);
|
||||
writel(INTR_MASTER_MASK, master->regs + INTR_STATUS_EN);
|
||||
writel(INTR_MASTER_MASK, master->regs + INTR_SIGNAL_EN);
|
||||
|
||||
ret = i3c_master_get_free_addr(m, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
goto rpm_out;
|
||||
|
||||
writel(DEV_ADDR_DYNAMIC_ADDR_VALID | DEV_ADDR_DYNAMIC(ret),
|
||||
master->regs + DEVICE_ADDR);
|
||||
|
||||
master->dev_addr = ret;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.dyn_addr = ret;
|
||||
|
||||
ret = i3c_master_set_info(&master->base, &info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
writel(IBI_REQ_REJECT_ALL, master->regs + IBI_SIR_REQ_REJECT);
|
||||
writel(IBI_REQ_REJECT_ALL, master->regs + IBI_MR_REQ_REJECT);
|
||||
|
||||
/* For now don't support Hot-Join */
|
||||
writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_HOT_JOIN_NACK,
|
||||
master->regs + DEVICE_CTRL);
|
||||
goto rpm_out;
|
||||
|
||||
dw_i3c_master_set_intr_regs(master);
|
||||
dw_i3c_master_enable(master);
|
||||
|
||||
return 0;
|
||||
rpm_out:
|
||||
pm_runtime_mark_last_busy(master->dev);
|
||||
pm_runtime_put_autosuspend(master->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dw_i3c_master_bus_cleanup(struct i3c_master_controller *m)
|
||||
@ -769,11 +803,21 @@ static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
if (ccc->id == I3C_CCC_ENTDAA)
|
||||
return -EINVAL;
|
||||
|
||||
ret = pm_runtime_resume_and_get(master->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(master->dev,
|
||||
"<%s> cannot resume i3c bus master, err: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ccc->rnw)
|
||||
ret = dw_i3c_ccc_get(master, ccc);
|
||||
else
|
||||
ret = dw_i3c_ccc_set(master, ccc);
|
||||
|
||||
pm_runtime_mark_last_busy(master->dev);
|
||||
pm_runtime_put_autosuspend(master->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -786,6 +830,14 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
|
||||
u8 p, last_addr = 0;
|
||||
int ret, pos;
|
||||
|
||||
ret = pm_runtime_resume_and_get(master->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(master->dev,
|
||||
"<%s> cannot resume i3c bus master, err: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
olddevs = ~(master->free_pos);
|
||||
|
||||
/* Prepare DAT before launching DAA. */
|
||||
@ -794,8 +846,10 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
|
||||
continue;
|
||||
|
||||
ret = i3c_master_get_free_addr(m, last_addr + 1);
|
||||
if (ret < 0)
|
||||
return -ENOSPC;
|
||||
if (ret < 0) {
|
||||
ret = -ENOSPC;
|
||||
goto rpm_out;
|
||||
}
|
||||
|
||||
master->devs[pos].addr = ret;
|
||||
p = even_parity(ret);
|
||||
@ -805,16 +859,21 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
|
||||
writel(DEV_ADDR_TABLE_DYNAMIC_ADDR(ret),
|
||||
master->regs +
|
||||
DEV_ADDR_TABLE_LOC(master->datstartaddr, pos));
|
||||
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
xfer = dw_i3c_master_alloc_xfer(master, 1);
|
||||
if (!xfer)
|
||||
return -ENOMEM;
|
||||
if (!xfer) {
|
||||
ret = -ENOMEM;
|
||||
goto rpm_out;
|
||||
}
|
||||
|
||||
pos = dw_i3c_master_get_free_pos(master);
|
||||
if (pos < 0) {
|
||||
dw_i3c_master_free_xfer(xfer);
|
||||
return pos;
|
||||
ret = pos;
|
||||
goto rpm_out;
|
||||
}
|
||||
cmd = &xfer->cmds[0];
|
||||
cmd->cmd_hi = 0x1;
|
||||
@ -839,7 +898,10 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
|
||||
|
||||
dw_i3c_master_free_xfer(xfer);
|
||||
|
||||
return 0;
|
||||
rpm_out:
|
||||
pm_runtime_mark_last_busy(master->dev);
|
||||
pm_runtime_put_autosuspend(master->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
|
||||
@ -874,6 +936,14 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
|
||||
if (!xfer)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = pm_runtime_resume_and_get(master->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(master->dev,
|
||||
"<%s> cannot resume i3c bus master, err: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < i3c_nxfers; i++) {
|
||||
struct dw_i3c_cmd *cmd = &xfer->cmds[i];
|
||||
|
||||
@ -915,6 +985,8 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
|
||||
ret = xfer->ret;
|
||||
dw_i3c_master_free_xfer(xfer);
|
||||
|
||||
pm_runtime_mark_last_busy(master->dev);
|
||||
pm_runtime_put_autosuspend(master->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1025,6 +1097,14 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
|
||||
if (!xfer)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = pm_runtime_resume_and_get(master->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(master->dev,
|
||||
"<%s> cannot resume i3c bus master, err: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < i2c_nxfers; i++) {
|
||||
struct dw_i3c_cmd *cmd = &xfer->cmds[i];
|
||||
|
||||
@ -1055,6 +1135,8 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
|
||||
ret = xfer->ret;
|
||||
dw_i3c_master_free_xfer(xfer);
|
||||
|
||||
pm_runtime_mark_last_busy(master->dev);
|
||||
pm_runtime_put_autosuspend(master->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1075,6 +1157,7 @@ static int dw_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
|
||||
|
||||
data->index = pos;
|
||||
master->devs[pos].addr = dev->addr;
|
||||
master->devs[pos].is_i2c_addr = true;
|
||||
master->free_pos &= ~BIT(pos);
|
||||
i2c_dev_set_master_data(dev, data);
|
||||
|
||||
@ -1175,17 +1258,16 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master,
|
||||
master->platform_ops->set_dat_ibi(master, dev, enable, ®);
|
||||
writel(reg, master->regs + dat_entry);
|
||||
|
||||
reg = readl(master->regs + IBI_SIR_REQ_REJECT);
|
||||
if (enable) {
|
||||
global = reg == 0xffffffff;
|
||||
reg &= ~BIT(idx);
|
||||
global = (master->sir_rej_mask == IBI_REQ_REJECT_ALL);
|
||||
master->sir_rej_mask &= ~BIT(idx);
|
||||
} else {
|
||||
bool hj_rejected = !!(readl(master->regs + DEVICE_CTRL) & DEV_CTRL_HOT_JOIN_NACK);
|
||||
|
||||
reg |= BIT(idx);
|
||||
global = (reg == 0xffffffff) && hj_rejected;
|
||||
master->sir_rej_mask |= BIT(idx);
|
||||
global = (master->sir_rej_mask == IBI_REQ_REJECT_ALL) && hj_rejected;
|
||||
}
|
||||
writel(reg, master->regs + IBI_SIR_REQ_REJECT);
|
||||
writel(master->sir_rej_mask, master->regs + IBI_SIR_REQ_REJECT);
|
||||
|
||||
if (global)
|
||||
dw_i3c_master_enable_sir_signal(master, enable);
|
||||
@ -1197,6 +1279,15 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master,
|
||||
static int dw_i3c_master_enable_hotjoin(struct i3c_master_controller *m)
|
||||
{
|
||||
struct dw_i3c_master *master = to_dw_i3c_master(m);
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_resume_and_get(master->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(master->dev,
|
||||
"<%s> cannot resume i3c bus master, err: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dw_i3c_master_enable_sir_signal(master, true);
|
||||
writel(readl(master->regs + DEVICE_CTRL) & ~DEV_CTRL_HOT_JOIN_NACK,
|
||||
@ -1212,6 +1303,8 @@ static int dw_i3c_master_disable_hotjoin(struct i3c_master_controller *m)
|
||||
writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_HOT_JOIN_NACK,
|
||||
master->regs + DEVICE_CTRL);
|
||||
|
||||
pm_runtime_mark_last_busy(master->dev);
|
||||
pm_runtime_put_autosuspend(master->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1222,12 +1315,23 @@ static int dw_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
|
||||
struct dw_i3c_master *master = to_dw_i3c_master(m);
|
||||
int rc;
|
||||
|
||||
rc = pm_runtime_resume_and_get(master->dev);
|
||||
if (rc < 0) {
|
||||
dev_err(master->dev,
|
||||
"<%s> cannot resume i3c bus master, err: %d\n",
|
||||
__func__, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
dw_i3c_master_set_sir_enabled(master, dev, data->index, true);
|
||||
|
||||
rc = i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
|
||||
|
||||
if (rc)
|
||||
if (rc) {
|
||||
dw_i3c_master_set_sir_enabled(master, dev, data->index, false);
|
||||
pm_runtime_mark_last_busy(master->dev);
|
||||
pm_runtime_put_autosuspend(master->dev);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
@ -1245,6 +1349,8 @@ static int dw_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
|
||||
|
||||
dw_i3c_master_set_sir_enabled(master, dev, data->index, false);
|
||||
|
||||
pm_runtime_mark_last_busy(master->dev);
|
||||
pm_runtime_put_autosuspend(master->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1403,21 +1509,6 @@ static const struct i3c_master_controller_ops dw_mipi_i3c_ops = {
|
||||
.attach_i2c_dev = dw_i3c_master_attach_i2c_dev,
|
||||
.detach_i2c_dev = dw_i3c_master_detach_i2c_dev,
|
||||
.i2c_xfers = dw_i3c_master_i2c_xfers,
|
||||
};
|
||||
|
||||
static const struct i3c_master_controller_ops dw_mipi_i3c_ibi_ops = {
|
||||
.bus_init = dw_i3c_master_bus_init,
|
||||
.bus_cleanup = dw_i3c_master_bus_cleanup,
|
||||
.attach_i3c_dev = dw_i3c_master_attach_i3c_dev,
|
||||
.reattach_i3c_dev = dw_i3c_master_reattach_i3c_dev,
|
||||
.detach_i3c_dev = dw_i3c_master_detach_i3c_dev,
|
||||
.do_daa = dw_i3c_master_daa,
|
||||
.supports_ccc_cmd = dw_i3c_master_supports_ccc_cmd,
|
||||
.send_ccc_cmd = dw_i3c_master_send_ccc_cmd,
|
||||
.priv_xfers = dw_i3c_master_priv_xfers,
|
||||
.attach_i2c_dev = dw_i3c_master_attach_i2c_dev,
|
||||
.detach_i2c_dev = dw_i3c_master_detach_i2c_dev,
|
||||
.i2c_xfers = dw_i3c_master_i2c_xfers,
|
||||
.request_ibi = dw_i3c_master_request_ibi,
|
||||
.free_ibi = dw_i3c_master_free_ibi,
|
||||
.enable_ibi = dw_i3c_master_enable_ibi,
|
||||
@ -1455,29 +1546,30 @@ static void dw_i3c_hj_work(struct work_struct *work)
|
||||
int dw_i3c_common_probe(struct dw_i3c_master *master,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
const struct i3c_master_controller_ops *ops;
|
||||
int ret, irq;
|
||||
|
||||
if (!master->platform_ops)
|
||||
master->platform_ops = &dw_i3c_platform_ops_default;
|
||||
|
||||
master->dev = &pdev->dev;
|
||||
|
||||
master->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(master->regs))
|
||||
return PTR_ERR(master->regs);
|
||||
|
||||
master->core_clk = devm_clk_get(&pdev->dev, NULL);
|
||||
master->core_clk = devm_clk_get_enabled(&pdev->dev, NULL);
|
||||
if (IS_ERR(master->core_clk))
|
||||
return PTR_ERR(master->core_clk);
|
||||
|
||||
master->pclk = devm_clk_get_optional_enabled(&pdev->dev, "pclk");
|
||||
if (IS_ERR(master->pclk))
|
||||
return PTR_ERR(master->pclk);
|
||||
|
||||
master->core_rst = devm_reset_control_get_optional_exclusive(&pdev->dev,
|
||||
"core_rst");
|
||||
if (IS_ERR(master->core_rst))
|
||||
return PTR_ERR(master->core_rst);
|
||||
|
||||
ret = clk_prepare_enable(master->core_clk);
|
||||
if (ret)
|
||||
goto err_disable_core_clk;
|
||||
|
||||
reset_control_deassert(master->core_rst);
|
||||
|
||||
spin_lock_init(&master->xferqueue.lock);
|
||||
@ -1493,6 +1585,11 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
|
||||
|
||||
platform_set_drvdata(pdev, master);
|
||||
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, RPM_AUTOSUSPEND_TIMEOUT);
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
pm_runtime_set_active(&pdev->dev);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
/* Information regarding the FIFOs/QUEUEs depth */
|
||||
ret = readl(master->regs + QUEUE_STATUS_LEVEL);
|
||||
master->caps.cmdfifodepth = QUEUE_STATUS_LEVEL_CMD(ret);
|
||||
@ -1505,23 +1602,22 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
|
||||
master->maxdevs = ret >> 16;
|
||||
master->free_pos = GENMASK(master->maxdevs - 1, 0);
|
||||
|
||||
ops = &dw_mipi_i3c_ops;
|
||||
if (master->ibi_capable)
|
||||
ops = &dw_mipi_i3c_ibi_ops;
|
||||
|
||||
INIT_WORK(&master->hj_work, dw_i3c_hj_work);
|
||||
ret = i3c_master_register(&master->base, &pdev->dev, ops, false);
|
||||
ret = i3c_master_register(&master->base, &pdev->dev,
|
||||
&dw_mipi_i3c_ops, false);
|
||||
if (ret)
|
||||
goto err_assert_rst;
|
||||
goto err_disable_pm;
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_pm:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_set_suspended(&pdev->dev);
|
||||
pm_runtime_dont_use_autosuspend(&pdev->dev);
|
||||
|
||||
err_assert_rst:
|
||||
reset_control_assert(master->core_rst);
|
||||
|
||||
err_disable_core_clk:
|
||||
clk_disable_unprepare(master->core_clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_i3c_common_probe);
|
||||
@ -1530,9 +1626,9 @@ void dw_i3c_common_remove(struct dw_i3c_master *master)
|
||||
{
|
||||
i3c_master_unregister(&master->base);
|
||||
|
||||
reset_control_assert(master->core_rst);
|
||||
|
||||
clk_disable_unprepare(master->core_clk);
|
||||
pm_runtime_disable(master->dev);
|
||||
pm_runtime_set_suspended(master->dev);
|
||||
pm_runtime_dont_use_autosuspend(master->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_i3c_common_remove);
|
||||
|
||||
@ -1556,6 +1652,96 @@ static void dw_i3c_remove(struct platform_device *pdev)
|
||||
dw_i3c_common_remove(master);
|
||||
}
|
||||
|
||||
static void dw_i3c_master_restore_addrs(struct dw_i3c_master *master)
|
||||
{
|
||||
u32 pos, reg_val;
|
||||
|
||||
writel(DEV_ADDR_DYNAMIC_ADDR_VALID | DEV_ADDR_DYNAMIC(master->dev_addr),
|
||||
master->regs + DEVICE_ADDR);
|
||||
|
||||
for (pos = 0; pos < master->maxdevs; pos++) {
|
||||
if (master->free_pos & BIT(pos))
|
||||
continue;
|
||||
|
||||
if (master->devs[pos].is_i2c_addr)
|
||||
reg_val = DEV_ADDR_TABLE_LEGACY_I2C_DEV |
|
||||
DEV_ADDR_TABLE_STATIC_ADDR(master->devs[pos].addr);
|
||||
else
|
||||
reg_val = DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr);
|
||||
|
||||
writel(reg_val, master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, pos));
|
||||
}
|
||||
}
|
||||
|
||||
static void dw_i3c_master_restore_timing_regs(struct dw_i3c_master *master)
|
||||
{
|
||||
writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING);
|
||||
writel(master->bus_free_timing, master->regs + BUS_FREE_TIMING);
|
||||
writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING);
|
||||
writel(master->ext_lcnt_timing, master->regs + SCL_EXT_LCNT_TIMING);
|
||||
|
||||
if (master->i2c_slv_prsnt) {
|
||||
writel(master->i2c_fmp_timing, master->regs + SCL_I2C_FMP_TIMING);
|
||||
writel(master->i2c_fm_timing, master->regs + SCL_I2C_FM_TIMING);
|
||||
}
|
||||
}
|
||||
|
||||
static int dw_i3c_master_enable_clks(struct dw_i3c_master *master)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = clk_prepare_enable(master->core_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(master->pclk);
|
||||
if (ret) {
|
||||
clk_disable_unprepare(master->core_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void dw_i3c_master_disable_clks(struct dw_i3c_master *master)
|
||||
{
|
||||
clk_disable_unprepare(master->pclk);
|
||||
clk_disable_unprepare(master->core_clk);
|
||||
}
|
||||
|
||||
static int __maybe_unused dw_i3c_master_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct dw_i3c_master *master = dev_get_drvdata(dev);
|
||||
|
||||
dw_i3c_master_disable(master);
|
||||
|
||||
reset_control_assert(master->core_rst);
|
||||
dw_i3c_master_disable_clks(master);
|
||||
pinctrl_pm_select_sleep_state(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused dw_i3c_master_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct dw_i3c_master *master = dev_get_drvdata(dev);
|
||||
|
||||
pinctrl_pm_select_default_state(dev);
|
||||
dw_i3c_master_enable_clks(master);
|
||||
reset_control_deassert(master->core_rst);
|
||||
|
||||
dw_i3c_master_set_intr_regs(master);
|
||||
dw_i3c_master_restore_timing_regs(master);
|
||||
dw_i3c_master_restore_addrs(master);
|
||||
|
||||
dw_i3c_master_enable(master);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops dw_i3c_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
|
||||
SET_RUNTIME_PM_OPS(dw_i3c_master_runtime_suspend, dw_i3c_master_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct of_device_id dw_i3c_master_of_match[] = {
|
||||
{ .compatible = "snps,dw-i3c-master-1.00a", },
|
||||
{},
|
||||
@ -1568,6 +1754,7 @@ static struct platform_driver dw_i3c_driver = {
|
||||
.driver = {
|
||||
.name = "dw-i3c-master",
|
||||
.of_match_table = dw_i3c_master_of_match,
|
||||
.pm = &dw_i3c_pm_ops,
|
||||
},
|
||||
};
|
||||
module_platform_driver(dw_i3c_driver);
|
||||
|
@ -19,11 +19,13 @@ struct dw_i3c_master_caps {
|
||||
|
||||
struct dw_i3c_dat_entry {
|
||||
u8 addr;
|
||||
bool is_i2c_addr;
|
||||
struct i3c_dev_desc *ibi_dev;
|
||||
};
|
||||
|
||||
struct dw_i3c_master {
|
||||
struct i3c_master_controller base;
|
||||
struct device *dev;
|
||||
u16 maxdevs;
|
||||
u16 datstartaddr;
|
||||
u32 free_pos;
|
||||
@ -36,10 +38,18 @@ struct dw_i3c_master {
|
||||
void __iomem *regs;
|
||||
struct reset_control *core_rst;
|
||||
struct clk *core_clk;
|
||||
struct clk *pclk;
|
||||
char version[5];
|
||||
char type[5];
|
||||
bool ibi_capable;
|
||||
|
||||
u32 sir_rej_mask;
|
||||
bool i2c_slv_prsnt;
|
||||
u32 dev_addr;
|
||||
u32 i3c_pp_timing;
|
||||
u32 i3c_od_timing;
|
||||
u32 ext_lcnt_timing;
|
||||
u32 bus_free_timing;
|
||||
u32 i2c_fm_timing;
|
||||
u32 i2c_fmp_timing;
|
||||
/*
|
||||
* Per-device hardware data, used to manage the device address table
|
||||
* (DAT)
|
||||
|
@ -631,6 +631,7 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
|
||||
static int i3c_hci_init(struct i3c_hci *hci)
|
||||
{
|
||||
u32 regval, offset;
|
||||
bool size_in_dwords;
|
||||
int ret;
|
||||
|
||||
/* Validate HCI hardware version */
|
||||
@ -654,11 +655,16 @@ static int i3c_hci_init(struct i3c_hci *hci)
|
||||
hci->caps = reg_read(HC_CAPABILITIES);
|
||||
DBG("caps = %#x", hci->caps);
|
||||
|
||||
size_in_dwords = hci->version_major < 1 ||
|
||||
(hci->version_major == 1 && hci->version_minor < 1);
|
||||
|
||||
regval = reg_read(DAT_SECTION);
|
||||
offset = FIELD_GET(DAT_TABLE_OFFSET, regval);
|
||||
hci->DAT_regs = offset ? hci->base_regs + offset : NULL;
|
||||
hci->DAT_entries = FIELD_GET(DAT_TABLE_SIZE, regval);
|
||||
hci->DAT_entry_size = FIELD_GET(DAT_ENTRY_SIZE, regval) ? 0 : 8;
|
||||
if (size_in_dwords)
|
||||
hci->DAT_entries = 4 * hci->DAT_entries / hci->DAT_entry_size;
|
||||
dev_info(&hci->master.dev, "DAT: %u %u-bytes entries at offset %#x\n",
|
||||
hci->DAT_entries, hci->DAT_entry_size, offset);
|
||||
|
||||
@ -667,6 +673,8 @@ static int i3c_hci_init(struct i3c_hci *hci)
|
||||
hci->DCT_regs = offset ? hci->base_regs + offset : NULL;
|
||||
hci->DCT_entries = FIELD_GET(DCT_TABLE_SIZE, regval);
|
||||
hci->DCT_entry_size = FIELD_GET(DCT_ENTRY_SIZE, regval) ? 0 : 16;
|
||||
if (size_in_dwords)
|
||||
hci->DCT_entries = 4 * hci->DCT_entries / hci->DCT_entry_size;
|
||||
dev_info(&hci->master.dev, "DCT: %u %u-bytes entries at offset %#x\n",
|
||||
hci->DCT_entries, hci->DCT_entry_size, offset);
|
||||
|
||||
|
@ -147,21 +147,6 @@ struct hci_dma_dev_ibi_data {
|
||||
unsigned int max_len;
|
||||
};
|
||||
|
||||
static inline u32 lo32(dma_addr_t physaddr)
|
||||
{
|
||||
return physaddr;
|
||||
}
|
||||
|
||||
static inline u32 hi32(dma_addr_t physaddr)
|
||||
{
|
||||
/* trickery to avoid compiler warnings on 32-bit build targets */
|
||||
if (sizeof(dma_addr_t) > 4) {
|
||||
u64 hi = physaddr;
|
||||
return hi >> 32;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hci_dma_cleanup(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
@ -265,10 +250,10 @@ static int hci_dma_init(struct i3c_hci *hci)
|
||||
if (!rh->xfer || !rh->resp || !rh->src_xfers)
|
||||
goto err_out;
|
||||
|
||||
rh_reg_write(CMD_RING_BASE_LO, lo32(rh->xfer_dma));
|
||||
rh_reg_write(CMD_RING_BASE_HI, hi32(rh->xfer_dma));
|
||||
rh_reg_write(RESP_RING_BASE_LO, lo32(rh->resp_dma));
|
||||
rh_reg_write(RESP_RING_BASE_HI, hi32(rh->resp_dma));
|
||||
rh_reg_write(CMD_RING_BASE_LO, lower_32_bits(rh->xfer_dma));
|
||||
rh_reg_write(CMD_RING_BASE_HI, upper_32_bits(rh->xfer_dma));
|
||||
rh_reg_write(RESP_RING_BASE_LO, lower_32_bits(rh->resp_dma));
|
||||
rh_reg_write(RESP_RING_BASE_HI, upper_32_bits(rh->resp_dma));
|
||||
|
||||
regval = FIELD_PREP(CR_RING_SIZE, rh->xfer_entries);
|
||||
rh_reg_write(CR_SETUP, regval);
|
||||
@ -294,7 +279,17 @@ static int hci_dma_init(struct i3c_hci *hci)
|
||||
|
||||
rh->ibi_chunk_sz = dma_get_cache_alignment();
|
||||
rh->ibi_chunk_sz *= IBI_CHUNK_CACHELINES;
|
||||
BUG_ON(rh->ibi_chunk_sz > 256);
|
||||
/*
|
||||
* Round IBI data chunk size to number of bytes supported by
|
||||
* the HW. Chunk size can be 2^n number of DWORDs which is the
|
||||
* same as 2^(n+2) bytes, where n is 0..6.
|
||||
*/
|
||||
rh->ibi_chunk_sz = umax(4, rh->ibi_chunk_sz);
|
||||
rh->ibi_chunk_sz = roundup_pow_of_two(rh->ibi_chunk_sz);
|
||||
if (rh->ibi_chunk_sz > 256) {
|
||||
ret = -EINVAL;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
ibi_status_ring_sz = rh->ibi_status_sz * rh->ibi_status_entries;
|
||||
ibi_data_ring_sz = rh->ibi_chunk_sz * rh->ibi_chunks_total;
|
||||
@ -315,6 +310,11 @@ static int hci_dma_init(struct i3c_hci *hci)
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
rh_reg_write(IBI_STATUS_RING_BASE_LO, lower_32_bits(rh->ibi_status_dma));
|
||||
rh_reg_write(IBI_STATUS_RING_BASE_HI, upper_32_bits(rh->ibi_status_dma));
|
||||
rh_reg_write(IBI_DATA_RING_BASE_LO, lower_32_bits(rh->ibi_data_dma));
|
||||
rh_reg_write(IBI_DATA_RING_BASE_HI, upper_32_bits(rh->ibi_data_dma));
|
||||
|
||||
regval = FIELD_PREP(IBI_STATUS_RING_SIZE,
|
||||
rh->ibi_status_entries) |
|
||||
FIELD_PREP(IBI_DATA_CHUNK_SIZE,
|
||||
@ -404,8 +404,8 @@ static int hci_dma_queue_xfer(struct i3c_hci *hci,
|
||||
hci_dma_unmap_xfer(hci, xfer_list, i);
|
||||
return -ENOMEM;
|
||||
}
|
||||
*ring_data++ = lo32(xfer->data_dma);
|
||||
*ring_data++ = hi32(xfer->data_dma);
|
||||
*ring_data++ = lower_32_bits(xfer->data_dma);
|
||||
*ring_data++ = upper_32_bits(xfer->data_dma);
|
||||
} else {
|
||||
*ring_data++ = 0;
|
||||
*ring_data++ = 0;
|
||||
|
@ -790,7 +790,20 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
|
||||
int ret, i;
|
||||
|
||||
while (true) {
|
||||
/* Enter/proceed with DAA */
|
||||
/* SVC_I3C_MCTRL_REQUEST_PROC_DAA have two mode, ENTER DAA or PROCESS DAA.
|
||||
*
|
||||
* ENTER DAA:
|
||||
* 1 will issue START, 7E, ENTDAA, and then emits 7E/R to process first target.
|
||||
* 2 Stops just before the new Dynamic Address (DA) is to be emitted.
|
||||
*
|
||||
* PROCESS DAA:
|
||||
* 1 The DA is written using MWDATAB or ADDR bits 6:0.
|
||||
* 2 ProcessDAA is requested again to write the new address, and then starts the
|
||||
* next (START, 7E, ENTDAA) unless marked to STOP; an MSTATUS indicating NACK
|
||||
* means DA was not accepted (e.g. parity error). If PROCESSDAA is NACKed on the
|
||||
* 7E/R, which means no more Slaves need a DA, then a COMPLETE will be signaled
|
||||
* (along with DONE), and a STOP issued automatically.
|
||||
*/
|
||||
writel(SVC_I3C_MCTRL_REQUEST_PROC_DAA |
|
||||
SVC_I3C_MCTRL_TYPE_I3C |
|
||||
SVC_I3C_MCTRL_IBIRESP_NACK |
|
||||
@ -807,7 +820,7 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
|
||||
SVC_I3C_MSTATUS_MCTRLDONE(reg),
|
||||
1, 1000);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
if (SVC_I3C_MSTATUS_RXPEND(reg)) {
|
||||
u8 data[6];
|
||||
@ -819,7 +832,7 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
|
||||
*/
|
||||
ret = svc_i3c_master_readb(master, data, 6);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
for (i = 0; i < 6; i++)
|
||||
prov_id[dev_nb] |= (u64)(data[i]) << (8 * (5 - i));
|
||||
@ -827,7 +840,7 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
|
||||
/* We do not care about the BCR and DCR yet */
|
||||
ret = svc_i3c_master_readb(master, data, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
} else if (SVC_I3C_MSTATUS_MCTRLDONE(reg)) {
|
||||
if (SVC_I3C_MSTATUS_STATE_IDLE(reg) &&
|
||||
SVC_I3C_MSTATUS_COMPLETE(reg)) {
|
||||
@ -835,12 +848,23 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
|
||||
* All devices received and acked they dynamic
|
||||
* address, this is the natural end of the DAA
|
||||
* procedure.
|
||||
*
|
||||
* Hardware will auto emit STOP at this case.
|
||||
*/
|
||||
break;
|
||||
*count = dev_nb;
|
||||
return 0;
|
||||
|
||||
} else if (SVC_I3C_MSTATUS_NACKED(reg)) {
|
||||
/* No I3C devices attached */
|
||||
if (dev_nb == 0)
|
||||
if (dev_nb == 0) {
|
||||
/*
|
||||
* Hardware can't treat first NACK for ENTAA as normal
|
||||
* COMPLETE. So need manual emit STOP.
|
||||
*/
|
||||
ret = 0;
|
||||
*count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* A slave device nacked the address, this is
|
||||
@ -849,8 +873,10 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
|
||||
* answer again immediately and shall ack the
|
||||
* address this time.
|
||||
*/
|
||||
if (prov_id[dev_nb] == nacking_prov_id)
|
||||
return -EIO;
|
||||
if (prov_id[dev_nb] == nacking_prov_id) {
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
dev_nb--;
|
||||
nacking_prov_id = prov_id[dev_nb];
|
||||
@ -858,7 +884,7 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
|
||||
|
||||
continue;
|
||||
} else {
|
||||
return -EIO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -870,12 +896,12 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
|
||||
SVC_I3C_MSTATUS_BETWEEN(reg),
|
||||
0, 1000);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
/* Give the slave device a suitable dynamic address */
|
||||
ret = i3c_master_get_free_addr(&master->base, last_addr + 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
addrs[dev_nb] = ret;
|
||||
dev_dbg(master->dev, "DAA: device %d assigned to 0x%02x\n",
|
||||
@ -885,9 +911,9 @@ static int svc_i3c_master_do_daa_locked(struct svc_i3c_master *master,
|
||||
last_addr = addrs[dev_nb++];
|
||||
}
|
||||
|
||||
*count = dev_nb;
|
||||
|
||||
return 0;
|
||||
/* Need manual issue STOP except for Complete condition */
|
||||
svc_i3c_master_emit_stop(master);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int svc_i3c_update_ibirules(struct svc_i3c_master *master)
|
||||
@ -961,11 +987,10 @@ static int svc_i3c_master_do_daa(struct i3c_master_controller *m)
|
||||
spin_lock_irqsave(&master->xferqueue.lock, flags);
|
||||
ret = svc_i3c_master_do_daa_locked(master, addrs, &dev_nb);
|
||||
spin_unlock_irqrestore(&master->xferqueue.lock, flags);
|
||||
if (ret) {
|
||||
svc_i3c_master_emit_stop(master);
|
||||
svc_i3c_master_clear_merrwarn(master);
|
||||
|
||||
svc_i3c_master_clear_merrwarn(master);
|
||||
if (ret)
|
||||
goto rpm_out;
|
||||
}
|
||||
|
||||
/* Register all devices who participated to the core */
|
||||
for (i = 0; i < dev_nb; i++) {
|
||||
@ -1052,29 +1077,59 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
|
||||
u8 *in, const u8 *out, unsigned int xfer_len,
|
||||
unsigned int *actual_len, bool continued)
|
||||
{
|
||||
int retry = 2;
|
||||
u32 reg;
|
||||
int ret;
|
||||
|
||||
/* clean SVC_I3C_MINT_IBIWON w1c bits */
|
||||
writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);
|
||||
|
||||
writel(SVC_I3C_MCTRL_REQUEST_START_ADDR |
|
||||
xfer_type |
|
||||
SVC_I3C_MCTRL_IBIRESP_NACK |
|
||||
SVC_I3C_MCTRL_DIR(rnw) |
|
||||
SVC_I3C_MCTRL_ADDR(addr) |
|
||||
SVC_I3C_MCTRL_RDTERM(*actual_len),
|
||||
master->regs + SVC_I3C_MCTRL);
|
||||
|
||||
ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
|
||||
while (retry--) {
|
||||
writel(SVC_I3C_MCTRL_REQUEST_START_ADDR |
|
||||
xfer_type |
|
||||
SVC_I3C_MCTRL_IBIRESP_NACK |
|
||||
SVC_I3C_MCTRL_DIR(rnw) |
|
||||
SVC_I3C_MCTRL_ADDR(addr) |
|
||||
SVC_I3C_MCTRL_RDTERM(*actual_len),
|
||||
master->regs + SVC_I3C_MCTRL);
|
||||
|
||||
ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
|
||||
SVC_I3C_MSTATUS_MCTRLDONE(reg), 0, 1000);
|
||||
if (ret)
|
||||
goto emit_stop;
|
||||
if (ret)
|
||||
goto emit_stop;
|
||||
|
||||
if (readl(master->regs + SVC_I3C_MERRWARN) & SVC_I3C_MERRWARN_NACK) {
|
||||
ret = -ENXIO;
|
||||
*actual_len = 0;
|
||||
goto emit_stop;
|
||||
if (readl(master->regs + SVC_I3C_MERRWARN) & SVC_I3C_MERRWARN_NACK) {
|
||||
/*
|
||||
* According to I3C Spec 1.1.1, 11-Jun-2021, section: 5.1.2.2.3.
|
||||
* If the Controller chooses to start an I3C Message with an I3C Dynamic
|
||||
* Address, then special provisions shall be made because that same I3C
|
||||
* Target may be initiating an IBI or a Controller Role Request. So, one of
|
||||
* three things may happen: (skip 1, 2)
|
||||
*
|
||||
* 3. The Addresses match and the RnW bits also match, and so neither
|
||||
* Controller nor Target will ACK since both are expecting the other side to
|
||||
* provide ACK. As a result, each side might think it had "won" arbitration,
|
||||
* but neither side would continue, as each would subsequently see that the
|
||||
* other did not provide ACK.
|
||||
* ...
|
||||
* For either value of RnW: Due to the NACK, the Controller shall defer the
|
||||
* Private Write or Private Read, and should typically transmit the Target
|
||||
* Address again after a Repeated START (i.e., the next one or any one prior
|
||||
* to a STOP in the Frame). Since the Address Header following a Repeated
|
||||
* START is not arbitrated, the Controller will always win (see Section
|
||||
* 5.1.2.2.4).
|
||||
*/
|
||||
if (retry && addr != 0x7e) {
|
||||
writel(SVC_I3C_MERRWARN_NACK, master->regs + SVC_I3C_MERRWARN);
|
||||
} else {
|
||||
ret = -ENXIO;
|
||||
*actual_len = 0;
|
||||
goto emit_stop;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1321,7 +1376,7 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master,
|
||||
cmd->addr = ccc->dests[0].addr;
|
||||
cmd->rnw = ccc->rnw;
|
||||
cmd->in = ccc->rnw ? ccc->dests[0].payload.data : NULL;
|
||||
cmd->out = ccc->rnw ? NULL : ccc->dests[0].payload.data,
|
||||
cmd->out = ccc->rnw ? NULL : ccc->dests[0].payload.data;
|
||||
cmd->len = xfer_len;
|
||||
cmd->actual_len = actual_len;
|
||||
cmd->continued = false;
|
||||
|
16
include/dt-bindings/i3c/i3c.h
Normal file
16
include/dt-bindings/i3c/i3c.h
Normal file
@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
|
||||
/*
|
||||
* Copyright 2024 NXP
|
||||
*/
|
||||
|
||||
#ifndef _DT_BINDINGS_I3C_I3C_H
|
||||
#define _DT_BINDINGS_I3C_I3C_H
|
||||
|
||||
#define I2C_FM (1 << 4)
|
||||
#define I2C_FM_PLUS (0 << 4)
|
||||
|
||||
#define I2C_FILTER (0 << 5)
|
||||
#define I2C_NO_FILTER_HIGH_FREQUENCY (1 << 5)
|
||||
#define I2C_NO_FILTER_LOW_FREQUENCY (2 << 5)
|
||||
|
||||
#endif
|
@ -33,6 +33,7 @@ enum {
|
||||
struct i3c_master_controller;
|
||||
struct i3c_bus;
|
||||
struct i3c_device;
|
||||
extern const struct bus_type i3c_bus_type;
|
||||
|
||||
/**
|
||||
* struct i3c_i2c_dev_desc - Common part of the I3C/I2C device descriptor
|
||||
|
Loading…
Reference in New Issue
Block a user