Driver to read from a register and depending on either set bits or a specific known selectively enable or disable OPPs based on DT node. Can support opp-modifier-reg-bit where single bits within the register determine the availability of an OPP or opp-modifier-reg-val where a certain value inside the register or a portion of it determine what the maximum allowed OPP is. The driver expects a device that has already has its OPPs loaded and then will disable the OPPs not matching the criteria specified in the opp-modifier table. Signed-off-by: Dave Gerlach <d-gerlach@xxxxxx> --- .../devicetree/bindings/power/opp-modifier.txt | 111 +++++++++ drivers/power/opp/Makefile | 1 + drivers/power/opp/opp-modifier-reg.c | 259 +++++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/opp-modifier.txt create mode 100644 drivers/power/opp/opp-modifier-reg.c diff --git a/Documentation/devicetree/bindings/power/opp-modifier.txt b/Documentation/devicetree/bindings/power/opp-modifier.txt new file mode 100644 index 0000000..af8a2e9 --- /dev/null +++ b/Documentation/devicetree/bindings/power/opp-modifier.txt @@ -0,0 +1,111 @@ +* OPP-Modifier - opp modifier to selectively enable operating points + +Many SoCs that have selectively modifiable OPPs can specify +all available OPPs in their operating-points listing and then define +opp_modifiers to enable or disable the OPPs that are actually available +on the specific hardware. + +* OPP Modifier Provider + +For single bits that define the availability of an OPP: +------------------------------------------- +Some SoCs define a bit in a register that indicates whether an OPP is +available. This will disable any OPP with a frequency corresponding to +the bit given if it is not set appropriately. + +properties: +- compatible : Should be "opp-modifier-reg-bit" +- reg : Address and length of the registers needed to identify available + OPPs, here we provide just the register containing OPP data. + +Optional Properties: +- opp,reg-bit-enable-low: Take the complement of register before comparing mask + defined below under opp-modifier. + +Sub-nodes: +Sub-nodes are defined as a container to hold opp modifier table for a +specific device with an operating-points table already defined + +Sub-node properties: +- opp-modifier: A collection of rows consisting of the following entries to + allow specification of available OPPs: + -kHz: The opp to be enabled based on following criteria + -offset: Offset into register where relevant bits are located + -value: Bit that indicates availability of OPP + +Example: + + opp_modifier: opp_modifier@0x44e107fc { + compatible = "opp-modifier-reg-bit"; + reg = <0x44e107fc 0x04>; + + mpu_opp_modifier: mpu_opp_modifier { + opp-modifier = < + /* kHz offset value */ + 1000000 0 BIT_1 + 720000 0 BIT_2 + >; + }; + }; + +For a value that defines the maximum available OPP: +------------------------------------------- +Some SoCs define a value in a register that corresponds to an OPP. If +that value is matched this will disable all OPPs greater than the +associated frequency. + +properties: +- compatible : Should be "opp-modifier-reg-val" +- reg : Address and length of the registers needed to identify available + OPPs, here we provide just the register containing OPP data. + +Optional Properties: +- opp,reg-mask: Only compare the bits masked off by this value. + +Sub-nodes: +Sub-nodes are defined as a container to hold opp modifier table for a +specific device with an operating-points table already defined + +Sub-node properties: +- opp-modifier: A collection of rows consisting of the following entries to + allow specification of available OPPs: + -kHz: The opp to be enabled based on following criteria + -offset: Offset into register where relevant bits are located + -value: Value that indicates maximum available OPP + +Example: + + opp_modifier: opp_modifier@0x44e107fc { + compatible = "opp-modifier-reg-val"; + reg = <0x44e107fc 0x04>; + + mpu_opp_modifier: mpu_opp_modifier { + opp-modifier = < + /* kHz offset value */ + 1000000 0 VAL_1 + 720000 0 VAL_2 + >; + }; + }; + +* OPP Modifier Consumer + +Properties: +- platform-opp-modifier: phandle to the sub-node of the proper opp-modifier + provider that contains the appropriate opp-modifier table + +Example: + +cpu@0 { + compatible = "arm,cortex-a8"; + device_type = "cpu"; + + operating-points = < + /* kHz uV */ + 1000000 1351000 + 720000 1285000 + >; + + platform-opp-modifier = <&mpu_opp_modifier>; +}; + diff --git a/drivers/power/opp/Makefile b/drivers/power/opp/Makefile index 820eb10..7f60adc 100644 --- a/drivers/power/opp/Makefile +++ b/drivers/power/opp/Makefile @@ -1 +1,2 @@ obj-y += core.o +obj-y += opp-modifier-reg.o diff --git a/drivers/power/opp/opp-modifier-reg.c b/drivers/power/opp/opp-modifier-reg.c new file mode 100644 index 0000000..f4dcf7a --- /dev/null +++ b/drivers/power/opp/opp-modifier-reg.c @@ -0,0 +1,259 @@ +/* + * Single bit OPP Modifier Driver + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/ + * Dave Gerlach <d-gerlach@xxxxxx> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/list.h> + +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_opp.h> +#include <linux/opp-modifier.h> + +static struct of_device_id opp_omap_of_match[]; + +struct opp_reg_context { + struct device *dev; + void __iomem *reg; + u32 mask; + bool enable_low; + int (*modify)(struct device *dev, const struct property *prop); +}; + +static struct opp_reg_context *opp_reg; + +static unsigned long opp_reg_read(int offset) +{ + return readl(opp_reg->reg + offset); +} + +static int opp_modifier_reg_bit_enable(struct device *dev, + const struct property *prop) +{ + const __be32 *val; + unsigned long reg_val, freq, offset, bit; + int idx; + + val = prop->value; + idx = (prop->length / sizeof(u32)) / 3; + while (idx--) { + freq = be32_to_cpup(val++) * 1000; + offset = be32_to_cpup(val++); + bit = be32_to_cpup(val++); + + reg_val = opp_reg_read(offset); + + if (opp_reg->enable_low) + reg_val = ~reg_val; + + if (!(reg_val & bit)) + dev_pm_opp_disable(dev, freq); + } + return 0; +} + +static int opp_modifier_reg_value_enable(struct device *dev, + const struct property *prop) +{ + const __be32 *val; + unsigned long reg_val, freq, offset, bits; + unsigned long disable_freq, search_freq; + struct dev_pm_opp *disable_opp; + int idx, i, opp_count; + + val = prop->value; + idx = (prop->length / sizeof(u32)) / 3; + + while (idx--) { + freq = be32_to_cpup(val++) * 1000; + offset = be32_to_cpup(val++); + bits = be32_to_cpup(val++); + + reg_val = opp_reg_read(offset); + + if ((reg_val & opp_reg->mask) == bits) { + /* + * Find all frequencies greater than current freq + */ + search_freq = freq + 1; + rcu_read_lock(); + opp_count = dev_pm_opp_get_opp_count(dev); + rcu_read_unlock(); + + for (i = 0; i < opp_count; i++) { + rcu_read_lock(); + disable_opp = + dev_pm_opp_find_freq_ceil(dev, + &search_freq); + if (IS_ERR(disable_opp)) { + rcu_read_unlock(); + break; + } + disable_freq = + dev_pm_opp_get_freq(disable_opp); + rcu_read_unlock(); + dev_pm_opp_disable(dev, disable_freq); + } + } + } + + return 0; +} + +static int of_opp_check_availability(struct device *dev, struct device_node *np) +{ + const struct property *prop; + int nr; + + if (!dev || !np) + return -EINVAL; + + prop = of_find_property(np, "opp-modifier", NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -EINVAL; + + nr = prop->length / sizeof(u32); + if (nr % 3) { + pr_err("%s: Invalid OPP Available list\n", __func__); + return -EINVAL; + } + + return opp_reg->modify(dev, prop); +} + +static int opp_modifier_reg_device_modify(struct device *dev) +{ + struct device_node *np; + int ret; + + if (!dev) + return -EINVAL; + + np = of_parse_phandle(dev->of_node, "platform-opp-modifier", 0); + + if (!np) + return -EINVAL; + + ret = of_opp_check_availability(dev, np); + + if (ret) + pr_err("Error modifying available OPPs\n"); + + of_node_put(np); + + return ret; +} + +static struct opp_modifier_ops opp_modifier_reg_ops = { + .modify = opp_modifier_reg_device_modify, +}; + +static struct opp_modifier_dev opp_modifier_reg_dev = { + .ops = &opp_modifier_reg_ops, +}; + +static struct of_device_id opp_modifier_reg_of_match[] = { + { + .compatible = "opp-modifier-reg-bit", + .data = &opp_modifier_reg_bit_enable, + }, + { + .compatible = "opp-modifier-reg-val", + .data = &opp_modifier_reg_value_enable, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, opp_modifier_reg_of_match); + +static int opp_modifier_reg_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct resource *res; + struct device_node *np = pdev->dev.of_node; + int ret = 0; + + opp_reg = devm_kzalloc(&pdev->dev, sizeof(*opp_reg), GFP_KERNEL); + if (!opp_reg) { + dev_err(opp_reg->dev, "reg context memory allocation failed\n"); + ret = -ENOMEM; + goto err; + } + + match = of_match_device(opp_modifier_reg_of_match, &pdev->dev); + + if (!match) { + dev_err(&pdev->dev, "Invalid match data value\n"); + ret = -EINVAL; + goto err; + } + + opp_reg->modify = (void *)match->data; + + opp_reg->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no memory resource for opp register\n"); + ret = -ENXIO; + goto err; + } + + opp_reg->reg = devm_request_and_ioremap(opp_reg->dev, res); + if (!opp_reg->reg) { + dev_err(opp_reg->dev, "could not ioremap opp register\n"); + ret = -EADDRNOTAVAIL; + goto err; + } + + if (of_get_property(np, "opp,reg-bit-enable-low", NULL)) + opp_reg->enable_low = true; + + of_property_read_u32(np, "opp,reg-mask", &opp_reg->mask); + + opp_modifier_reg_dev.ops = &opp_modifier_reg_ops; + opp_modifier_reg_dev.of_node = pdev->dev.of_node; + + opp_modifier_register(&opp_modifier_reg_dev); + +err: + return ret; +} + +static int opp_modifier_reg_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver opp_modifier_reg_driver = { + .probe = opp_modifier_reg_probe, + .remove = opp_modifier_reg_remove, + .driver = { + .owner = THIS_MODULE, + .name = "opp-modifier-reg", + .of_match_table = opp_modifier_reg_of_match, + }, +}; + +module_platform_driver(opp_modifier_reg_driver); + +MODULE_AUTHOR("Dave Gerlach <d-gerlach@xxxxxx>"); +MODULE_DESCRIPTION("OPP Modifier driver for eFuse defined OPPs"); +MODULE_LICENSE("GPL v2"); -- 1.9.0 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html