mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2024-12-28 16:53:49 +00:00
259b93b21a
Probing of regulators can be a slow operation and can contribute to slower boot times. This is especially true if a regulator is turned on at probe time (with regulator-boot-on or regulator-always-on) and the regulator requires delays (off-on-time, ramp time, etc). While the overall kernel is not ready to switch to async probe by default, as per the discussion on the mailing lists [1] it is believed that the regulator subsystem is in good shape and we can move regulator drivers over wholesale. There is no way to just magically opt in all regulators (regulators are just normal drivers like platform_driver), so we set PROBE_PREFER_ASYNCHRONOUS for all regulators found in 'drivers/regulator' individually. Given the number of drivers touched and the impossibility to test this ahead of time, it wouldn't be shocking at all if this caused a regression for someone. If there is a regression caused by this patch, it's likely to be one of the cases talked about in [1]. As a "quick fix", drivers involved in the regression could be fixed by changing them to PROBE_FORCE_SYNCHRONOUS. That being said, the correct fix would be to directly fix the problem that caused the issue with async probe. The approach here follows a similar approach that was used for the mmc subsystem several years ago [2]. In fact, I ran nearly the same python script to auto-generate the changes. The only thing I changed was to search for "i2c_driver", "spmi_driver", and "spi_driver" in addition to "platform_driver". [1] https://lore.kernel.org/r/06db017f-e985-4434-8d1d-02ca2100cca0@sirena.org.uk [2] https://lore.kernel.org/r/20200903232441.2694866-1-dianders@chromium.org/ Signed-off-by: Douglas Anderson <dianders@chromium.org> Link: https://lore.kernel.org/r/20230316125351.1.I2a4677392a38db5758dee0788b2cea5872562a82@changeid Signed-off-by: Mark Brown <broonie@kernel.org>
354 lines
9.6 KiB
C
354 lines
9.6 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
//
|
|
// Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/regulator/machine.h>
|
|
|
|
#define LDO_RAMP_UP_UNIT_IN_CYCLES 64 /* 64 cycles per step */
|
|
#define LDO_RAMP_UP_FREQ_IN_MHZ 24 /* cycle based on 24M OSC */
|
|
|
|
#define LDO_POWER_GATE 0x00
|
|
#define LDO_FET_FULL_ON 0x1f
|
|
|
|
struct anatop_regulator {
|
|
u32 delay_reg;
|
|
int delay_bit_shift;
|
|
int delay_bit_width;
|
|
struct regulator_desc rdesc;
|
|
bool bypass;
|
|
int sel;
|
|
};
|
|
|
|
static int anatop_regmap_set_voltage_time_sel(struct regulator_dev *reg,
|
|
unsigned int old_sel,
|
|
unsigned int new_sel)
|
|
{
|
|
struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg);
|
|
u32 val;
|
|
int ret = 0;
|
|
|
|
/* check whether need to care about LDO ramp up speed */
|
|
if (anatop_reg->delay_bit_width && new_sel > old_sel) {
|
|
/*
|
|
* the delay for LDO ramp up time is
|
|
* based on the register setting, we need
|
|
* to calculate how many steps LDO need to
|
|
* ramp up, and how much delay needed. (us)
|
|
*/
|
|
regmap_read(reg->regmap, anatop_reg->delay_reg, &val);
|
|
val = (val >> anatop_reg->delay_bit_shift) &
|
|
((1 << anatop_reg->delay_bit_width) - 1);
|
|
ret = (new_sel - old_sel) * (LDO_RAMP_UP_UNIT_IN_CYCLES <<
|
|
val) / LDO_RAMP_UP_FREQ_IN_MHZ + 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int anatop_regmap_enable(struct regulator_dev *reg)
|
|
{
|
|
struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg);
|
|
int sel;
|
|
|
|
sel = anatop_reg->bypass ? LDO_FET_FULL_ON : anatop_reg->sel;
|
|
return regulator_set_voltage_sel_regmap(reg, sel);
|
|
}
|
|
|
|
static int anatop_regmap_disable(struct regulator_dev *reg)
|
|
{
|
|
return regulator_set_voltage_sel_regmap(reg, LDO_POWER_GATE);
|
|
}
|
|
|
|
static int anatop_regmap_is_enabled(struct regulator_dev *reg)
|
|
{
|
|
return regulator_get_voltage_sel_regmap(reg) != LDO_POWER_GATE;
|
|
}
|
|
|
|
static int anatop_regmap_core_set_voltage_sel(struct regulator_dev *reg,
|
|
unsigned selector)
|
|
{
|
|
struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg);
|
|
int ret;
|
|
|
|
if (anatop_reg->bypass || !anatop_regmap_is_enabled(reg)) {
|
|
anatop_reg->sel = selector;
|
|
return 0;
|
|
}
|
|
|
|
ret = regulator_set_voltage_sel_regmap(reg, selector);
|
|
if (!ret)
|
|
anatop_reg->sel = selector;
|
|
return ret;
|
|
}
|
|
|
|
static int anatop_regmap_core_get_voltage_sel(struct regulator_dev *reg)
|
|
{
|
|
struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg);
|
|
|
|
if (anatop_reg->bypass || !anatop_regmap_is_enabled(reg))
|
|
return anatop_reg->sel;
|
|
|
|
return regulator_get_voltage_sel_regmap(reg);
|
|
}
|
|
|
|
static int anatop_regmap_get_bypass(struct regulator_dev *reg, bool *enable)
|
|
{
|
|
struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg);
|
|
int sel;
|
|
|
|
sel = regulator_get_voltage_sel_regmap(reg);
|
|
if (sel == LDO_FET_FULL_ON)
|
|
WARN_ON(!anatop_reg->bypass);
|
|
else if (sel != LDO_POWER_GATE)
|
|
WARN_ON(anatop_reg->bypass);
|
|
|
|
*enable = anatop_reg->bypass;
|
|
return 0;
|
|
}
|
|
|
|
static int anatop_regmap_set_bypass(struct regulator_dev *reg, bool enable)
|
|
{
|
|
struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg);
|
|
int sel;
|
|
|
|
if (enable == anatop_reg->bypass)
|
|
return 0;
|
|
|
|
sel = enable ? LDO_FET_FULL_ON : anatop_reg->sel;
|
|
anatop_reg->bypass = enable;
|
|
|
|
return regulator_set_voltage_sel_regmap(reg, sel);
|
|
}
|
|
|
|
static struct regulator_ops anatop_rops = {
|
|
.set_voltage_sel = regulator_set_voltage_sel_regmap,
|
|
.get_voltage_sel = regulator_get_voltage_sel_regmap,
|
|
.list_voltage = regulator_list_voltage_linear,
|
|
.map_voltage = regulator_map_voltage_linear,
|
|
};
|
|
|
|
static const struct regulator_ops anatop_core_rops = {
|
|
.enable = anatop_regmap_enable,
|
|
.disable = anatop_regmap_disable,
|
|
.is_enabled = anatop_regmap_is_enabled,
|
|
.set_voltage_sel = anatop_regmap_core_set_voltage_sel,
|
|
.set_voltage_time_sel = anatop_regmap_set_voltage_time_sel,
|
|
.get_voltage_sel = anatop_regmap_core_get_voltage_sel,
|
|
.list_voltage = regulator_list_voltage_linear,
|
|
.map_voltage = regulator_map_voltage_linear,
|
|
.get_bypass = anatop_regmap_get_bypass,
|
|
.set_bypass = anatop_regmap_set_bypass,
|
|
};
|
|
|
|
static int anatop_regulator_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct device_node *anatop_np;
|
|
struct regulator_desc *rdesc;
|
|
struct regulator_dev *rdev;
|
|
struct anatop_regulator *sreg;
|
|
struct regulator_init_data *initdata;
|
|
struct regulator_config config = { };
|
|
struct regmap *regmap;
|
|
u32 control_reg;
|
|
u32 vol_bit_shift;
|
|
u32 vol_bit_width;
|
|
u32 min_bit_val;
|
|
u32 min_voltage;
|
|
u32 max_voltage;
|
|
int ret = 0;
|
|
u32 val;
|
|
|
|
sreg = devm_kzalloc(dev, sizeof(*sreg), GFP_KERNEL);
|
|
if (!sreg)
|
|
return -ENOMEM;
|
|
|
|
rdesc = &sreg->rdesc;
|
|
rdesc->type = REGULATOR_VOLTAGE;
|
|
rdesc->owner = THIS_MODULE;
|
|
|
|
of_property_read_string(np, "regulator-name", &rdesc->name);
|
|
if (!rdesc->name) {
|
|
dev_err(dev, "failed to get a regulator-name\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
initdata = of_get_regulator_init_data(dev, np, rdesc);
|
|
if (!initdata)
|
|
return -ENOMEM;
|
|
|
|
initdata->supply_regulator = "vin";
|
|
|
|
anatop_np = of_get_parent(np);
|
|
if (!anatop_np)
|
|
return -ENODEV;
|
|
regmap = syscon_node_to_regmap(anatop_np);
|
|
of_node_put(anatop_np);
|
|
if (IS_ERR(regmap))
|
|
return PTR_ERR(regmap);
|
|
|
|
ret = of_property_read_u32(np, "anatop-reg-offset", &control_reg);
|
|
if (ret) {
|
|
dev_err(dev, "no anatop-reg-offset property set\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(np, "anatop-vol-bit-width", &vol_bit_width);
|
|
if (ret) {
|
|
dev_err(dev, "no anatop-vol-bit-width property set\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(np, "anatop-vol-bit-shift", &vol_bit_shift);
|
|
if (ret) {
|
|
dev_err(dev, "no anatop-vol-bit-shift property set\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(np, "anatop-min-bit-val", &min_bit_val);
|
|
if (ret) {
|
|
dev_err(dev, "no anatop-min-bit-val property set\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(np, "anatop-min-voltage", &min_voltage);
|
|
if (ret) {
|
|
dev_err(dev, "no anatop-min-voltage property set\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(np, "anatop-max-voltage", &max_voltage);
|
|
if (ret) {
|
|
dev_err(dev, "no anatop-max-voltage property set\n");
|
|
return ret;
|
|
}
|
|
|
|
/* read LDO ramp up setting, only for core reg */
|
|
of_property_read_u32(np, "anatop-delay-reg-offset",
|
|
&sreg->delay_reg);
|
|
of_property_read_u32(np, "anatop-delay-bit-width",
|
|
&sreg->delay_bit_width);
|
|
of_property_read_u32(np, "anatop-delay-bit-shift",
|
|
&sreg->delay_bit_shift);
|
|
|
|
rdesc->n_voltages = (max_voltage - min_voltage) / 25000 + 1
|
|
+ min_bit_val;
|
|
rdesc->min_uV = min_voltage;
|
|
rdesc->uV_step = 25000;
|
|
rdesc->linear_min_sel = min_bit_val;
|
|
rdesc->vsel_reg = control_reg;
|
|
rdesc->vsel_mask = ((1 << vol_bit_width) - 1) << vol_bit_shift;
|
|
rdesc->min_dropout_uV = 125000;
|
|
|
|
config.dev = &pdev->dev;
|
|
config.init_data = initdata;
|
|
config.driver_data = sreg;
|
|
config.of_node = pdev->dev.of_node;
|
|
config.regmap = regmap;
|
|
|
|
/* Only core regulators have the ramp up delay configuration. */
|
|
if (control_reg && sreg->delay_bit_width) {
|
|
rdesc->ops = &anatop_core_rops;
|
|
|
|
ret = regmap_read(config.regmap, rdesc->vsel_reg, &val);
|
|
if (ret) {
|
|
dev_err(dev, "failed to read initial state\n");
|
|
return ret;
|
|
}
|
|
|
|
sreg->sel = (val & rdesc->vsel_mask) >> vol_bit_shift;
|
|
if (sreg->sel == LDO_FET_FULL_ON) {
|
|
sreg->sel = 0;
|
|
sreg->bypass = true;
|
|
}
|
|
|
|
/*
|
|
* In case vddpu was disabled by the bootloader, we need to set
|
|
* a sane default until imx6-cpufreq was probed and changes the
|
|
* voltage to the correct value. In this case we set 1.25V.
|
|
*/
|
|
if (!sreg->sel && !strcmp(rdesc->name, "vddpu"))
|
|
sreg->sel = 22;
|
|
|
|
/* set the default voltage of the pcie phy to be 1.100v */
|
|
if (!sreg->sel && !strcmp(rdesc->name, "vddpcie"))
|
|
sreg->sel = 0x10;
|
|
|
|
if (!sreg->bypass && !sreg->sel) {
|
|
dev_err(&pdev->dev, "Failed to read a valid default voltage selector.\n");
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
u32 enable_bit;
|
|
|
|
rdesc->ops = &anatop_rops;
|
|
|
|
if (!of_property_read_u32(np, "anatop-enable-bit",
|
|
&enable_bit)) {
|
|
anatop_rops.enable = regulator_enable_regmap;
|
|
anatop_rops.disable = regulator_disable_regmap;
|
|
anatop_rops.is_enabled = regulator_is_enabled_regmap;
|
|
|
|
rdesc->enable_reg = control_reg;
|
|
rdesc->enable_mask = BIT(enable_bit);
|
|
}
|
|
}
|
|
|
|
/* register regulator */
|
|
rdev = devm_regulator_register(dev, rdesc, &config);
|
|
if (IS_ERR(rdev)) {
|
|
ret = PTR_ERR(rdev);
|
|
if (ret == -EPROBE_DEFER)
|
|
dev_dbg(dev, "failed to register %s, deferring...\n",
|
|
rdesc->name);
|
|
else
|
|
dev_err(dev, "failed to register %s\n", rdesc->name);
|
|
return ret;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, rdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id of_anatop_regulator_match_tbl[] = {
|
|
{ .compatible = "fsl,anatop-regulator", },
|
|
{ /* end */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, of_anatop_regulator_match_tbl);
|
|
|
|
static struct platform_driver anatop_regulator_driver = {
|
|
.driver = {
|
|
.name = "anatop_regulator",
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
.of_match_table = of_anatop_regulator_match_tbl,
|
|
},
|
|
.probe = anatop_regulator_probe,
|
|
};
|
|
|
|
static int __init anatop_regulator_init(void)
|
|
{
|
|
return platform_driver_register(&anatop_regulator_driver);
|
|
}
|
|
postcore_initcall(anatop_regulator_init);
|
|
|
|
static void __exit anatop_regulator_exit(void)
|
|
{
|
|
platform_driver_unregister(&anatop_regulator_driver);
|
|
}
|
|
module_exit(anatop_regulator_exit);
|
|
|
|
MODULE_AUTHOR("Nancy Chen <Nancy.Chen@freescale.com>");
|
|
MODULE_AUTHOR("Ying-Chun Liu (PaulLiu) <paul.liu@linaro.org>");
|
|
MODULE_DESCRIPTION("ANATOP Regulator driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:anatop_regulator");
|