0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #include <linux/cpu.h>
0013 #include <linux/device.h>
0014 #include <linux/errno.h>
0015 #include <linux/of.h>
0016 #include <linux/pm_opp.h>
0017 #include <linux/regmap.h>
0018 #include <linux/soc/samsung/exynos-chipid.h>
0019
0020 #include "exynos-asv.h"
0021 #include "exynos5422-asv.h"
0022
0023 #define MHZ 1000000U
0024
0025 static int exynos_asv_update_cpu_opps(struct exynos_asv *asv,
0026 struct device *cpu)
0027 {
0028 struct exynos_asv_subsys *subsys = NULL;
0029 struct dev_pm_opp *opp;
0030 unsigned int opp_freq;
0031 int i;
0032
0033 for (i = 0; i < ARRAY_SIZE(asv->subsys); i++) {
0034 if (of_device_is_compatible(cpu->of_node,
0035 asv->subsys[i].cpu_dt_compat)) {
0036 subsys = &asv->subsys[i];
0037 break;
0038 }
0039 }
0040 if (!subsys)
0041 return -EINVAL;
0042
0043 for (i = 0; i < subsys->table.num_rows; i++) {
0044 unsigned int new_volt, volt;
0045 int ret;
0046
0047 opp_freq = exynos_asv_opp_get_frequency(subsys, i);
0048
0049 opp = dev_pm_opp_find_freq_exact(cpu, opp_freq * MHZ, true);
0050 if (IS_ERR(opp)) {
0051 dev_info(asv->dev, "cpu%d opp%d, freq: %u missing\n",
0052 cpu->id, i, opp_freq);
0053
0054 continue;
0055 }
0056
0057 volt = dev_pm_opp_get_voltage(opp);
0058 new_volt = asv->opp_get_voltage(subsys, i, volt);
0059 dev_pm_opp_put(opp);
0060
0061 if (new_volt == volt)
0062 continue;
0063
0064 ret = dev_pm_opp_adjust_voltage(cpu, opp_freq * MHZ,
0065 new_volt, new_volt, new_volt);
0066 if (ret < 0)
0067 dev_err(asv->dev,
0068 "Failed to adjust OPP %u Hz/%u uV for cpu%d\n",
0069 opp_freq, new_volt, cpu->id);
0070 else
0071 dev_dbg(asv->dev,
0072 "Adjusted OPP %u Hz/%u -> %u uV, cpu%d\n",
0073 opp_freq, volt, new_volt, cpu->id);
0074 }
0075
0076 return 0;
0077 }
0078
0079 static int exynos_asv_update_opps(struct exynos_asv *asv)
0080 {
0081 struct opp_table *last_opp_table = NULL;
0082 struct device *cpu;
0083 int ret, cpuid;
0084
0085 for_each_possible_cpu(cpuid) {
0086 struct opp_table *opp_table;
0087
0088 cpu = get_cpu_device(cpuid);
0089 if (!cpu)
0090 continue;
0091
0092 opp_table = dev_pm_opp_get_opp_table(cpu);
0093 if (IS_ERR(opp_table))
0094 continue;
0095
0096 if (!last_opp_table || opp_table != last_opp_table) {
0097 last_opp_table = opp_table;
0098
0099 ret = exynos_asv_update_cpu_opps(asv, cpu);
0100 if (ret < 0)
0101 dev_err(asv->dev, "Couldn't udate OPPs for cpu%d\n",
0102 cpuid);
0103 }
0104
0105 dev_pm_opp_put_opp_table(opp_table);
0106 }
0107
0108 return 0;
0109 }
0110
0111 int exynos_asv_init(struct device *dev, struct regmap *regmap)
0112 {
0113 int (*probe_func)(struct exynos_asv *asv);
0114 struct exynos_asv *asv;
0115 struct device *cpu_dev;
0116 u32 product_id = 0;
0117 int ret, i;
0118
0119 asv = devm_kzalloc(dev, sizeof(*asv), GFP_KERNEL);
0120 if (!asv)
0121 return -ENOMEM;
0122
0123 asv->chipid_regmap = regmap;
0124 asv->dev = dev;
0125 ret = regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PRO_ID,
0126 &product_id);
0127 if (ret < 0) {
0128 dev_err(dev, "Cannot read revision from ChipID: %d\n", ret);
0129 return -ENODEV;
0130 }
0131
0132 switch (product_id & EXYNOS_MASK) {
0133 case 0xE5422000:
0134 probe_func = exynos5422_asv_init;
0135 break;
0136 default:
0137 dev_dbg(dev, "No ASV support for this SoC\n");
0138 devm_kfree(dev, asv);
0139 return 0;
0140 }
0141
0142 cpu_dev = get_cpu_device(0);
0143 ret = dev_pm_opp_get_opp_count(cpu_dev);
0144 if (ret < 0)
0145 return -EPROBE_DEFER;
0146
0147 ret = of_property_read_u32(dev->of_node, "samsung,asv-bin",
0148 &asv->of_bin);
0149 if (ret < 0)
0150 asv->of_bin = -EINVAL;
0151
0152 for (i = 0; i < ARRAY_SIZE(asv->subsys); i++)
0153 asv->subsys[i].asv = asv;
0154
0155 ret = probe_func(asv);
0156 if (ret < 0)
0157 return ret;
0158
0159 return exynos_asv_update_opps(asv);
0160 }