0001
0002
0003
0004
0005
0006
0007 #include <linux/err.h>
0008 #include <linux/kernel.h>
0009 #include <linux/pm_opp.h>
0010
0011 #include "cvb.h"
0012
0013
0014 static inline int get_cvb_voltage(int speedo, int s_scale,
0015 const struct cvb_coefficients *cvb)
0016 {
0017 int mv;
0018
0019
0020 mv = DIV_ROUND_CLOSEST(cvb->c2 * speedo, s_scale);
0021 mv = DIV_ROUND_CLOSEST((mv + cvb->c1) * speedo, s_scale) + cvb->c0;
0022 return mv;
0023 }
0024
0025 static int round_cvb_voltage(int mv, int v_scale,
0026 const struct rail_alignment *align)
0027 {
0028
0029 int uv;
0030 int step = (align->step_uv ? : 1000) * v_scale;
0031 int offset = align->offset_uv * v_scale;
0032
0033 uv = max(mv * 1000, offset) - offset;
0034 uv = DIV_ROUND_UP(uv, step) * align->step_uv + align->offset_uv;
0035 return uv / 1000;
0036 }
0037
0038 enum {
0039 DOWN,
0040 UP
0041 };
0042
0043 static int round_voltage(int mv, const struct rail_alignment *align, int up)
0044 {
0045 if (align->step_uv) {
0046 int uv;
0047
0048 uv = max(mv * 1000, align->offset_uv) - align->offset_uv;
0049 uv = (uv + (up ? align->step_uv - 1 : 0)) / align->step_uv;
0050 return (uv * align->step_uv + align->offset_uv) / 1000;
0051 }
0052 return mv;
0053 }
0054
0055 static int build_opp_table(struct device *dev, const struct cvb_table *table,
0056 struct rail_alignment *align,
0057 int speedo_value, unsigned long max_freq)
0058 {
0059 int i, ret, dfll_mv, min_mv, max_mv;
0060
0061 min_mv = round_voltage(table->min_millivolts, align, UP);
0062 max_mv = round_voltage(table->max_millivolts, align, DOWN);
0063
0064 for (i = 0; i < MAX_DVFS_FREQS; i++) {
0065 const struct cvb_table_freq_entry *entry = &table->entries[i];
0066
0067 if (!entry->freq || (entry->freq > max_freq))
0068 break;
0069
0070 dfll_mv = get_cvb_voltage(speedo_value, table->speedo_scale,
0071 &entry->coefficients);
0072 dfll_mv = round_cvb_voltage(dfll_mv, table->voltage_scale,
0073 align);
0074 dfll_mv = clamp(dfll_mv, min_mv, max_mv);
0075
0076 ret = dev_pm_opp_add(dev, entry->freq, dfll_mv * 1000);
0077 if (ret)
0078 return ret;
0079 }
0080
0081 return 0;
0082 }
0083
0084
0085
0086
0087
0088
0089
0090
0091
0092
0093
0094
0095
0096
0097
0098
0099
0100
0101
0102 const struct cvb_table *
0103 tegra_cvb_add_opp_table(struct device *dev, const struct cvb_table *tables,
0104 size_t count, struct rail_alignment *align,
0105 int process_id, int speedo_id, int speedo_value,
0106 unsigned long max_freq)
0107 {
0108 size_t i;
0109 int ret;
0110
0111 for (i = 0; i < count; i++) {
0112 const struct cvb_table *table = &tables[i];
0113
0114 if (table->speedo_id != -1 && table->speedo_id != speedo_id)
0115 continue;
0116
0117 if (table->process_id != -1 && table->process_id != process_id)
0118 continue;
0119
0120 ret = build_opp_table(dev, table, align, speedo_value,
0121 max_freq);
0122 return ret ? ERR_PTR(ret) : table;
0123 }
0124
0125 return ERR_PTR(-EINVAL);
0126 }
0127
0128 void tegra_cvb_remove_opp_table(struct device *dev,
0129 const struct cvb_table *table,
0130 unsigned long max_freq)
0131 {
0132 unsigned int i;
0133
0134 for (i = 0; i < MAX_DVFS_FREQS; i++) {
0135 const struct cvb_table_freq_entry *entry = &table->entries[i];
0136
0137 if (!entry->freq || (entry->freq > max_freq))
0138 break;
0139
0140 dev_pm_opp_remove(dev, entry->freq);
0141 }
0142 }