Message ID | 20240905074557.3810026-2-carlos.song@nxp.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | [V4,1/2] i3c: master: support to adjust first broadcast address speed | expand |
On Thu, Sep 05, 2024 at 03:45:57PM +0800, Carlos Song wrote: > I3C controller should support adjusting open drain timing for the first > broadcast address to make I3C device working as a i2c device can see slow > broadcast address to close its Spike Filter to change working at i3c mode. > > Signed-off-by: Carlos Song <carlos.song@nxp.com> > Reviewed-by: Frank Li <frank.li@nxp.com> Tested with https://lore.kernel.org/linux-i3c/20240719080233.842771-1-carlos.song@nxp.com/ The patch of i3c: master: svc: adjust SDR according to i3c spec is critial at imx93-9x9-qsb boards. Frank > --- > Change for V4: > - No change. Send out together with I3C master.c fix patch. > Change for V3: > - No change. But miss sending it with I3C master.c fix patch. > Change for V2: > - Adjust variable definition order > - Add mctrl_config description to fix build warning > --- > drivers/i3c/master/svc-i3c-master.c | 52 +++++++++++++++++++++++++++++ > 1 file changed, 52 insertions(+) > > diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c > index 7c30f58e344b..423db3dca257 100644 > --- a/drivers/i3c/master/svc-i3c-master.c > +++ b/drivers/i3c/master/svc-i3c-master.c > @@ -184,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; > @@ -214,6 +215,7 @@ struct svc_i3c_master { > } ibi; > struct mutex lock; > int enabled_events; > + u32 mctrl_config; > }; > > /** > @@ -531,6 +533,54 @@ 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); > @@ -624,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) > @@ -1658,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) > -- > 2.34.1 >
diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 7c30f58e344b..423db3dca257 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -184,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; @@ -214,6 +215,7 @@ struct svc_i3c_master { } ibi; struct mutex lock; int enabled_events; + u32 mctrl_config; }; /** @@ -531,6 +533,54 @@ 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); @@ -624,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) @@ -1658,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)