aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pwm')
-rw-r--r--drivers/pwm/pwm-sti.c140
1 files changed, 96 insertions, 44 deletions
diff --git a/drivers/pwm/pwm-sti.c b/drivers/pwm/pwm-sti.c
index 76346e32f403..2d3260571b5a 100644
--- a/drivers/pwm/pwm-sti.c
+++ b/drivers/pwm/pwm-sti.c
@@ -58,6 +58,7 @@ struct sti_pwm_chip {
58 struct regmap_field *pwm_int_en; 58 struct regmap_field *pwm_int_en;
59 unsigned long *pwm_periods; 59 unsigned long *pwm_periods;
60 struct pwm_chip chip; 60 struct pwm_chip chip;
61 struct pwm_device *cur;
61 void __iomem *mmio; 62 void __iomem *mmio;
62}; 63};
63 64
@@ -99,6 +100,24 @@ static void sti_pwm_calc_periods(struct sti_pwm_chip *pc)
99 } 100 }
100} 101}
101 102
103/* Calculate the number of PWM devices configured with a period. */
104static unsigned int sti_pwm_count_configured(struct pwm_chip *chip)
105{
106 struct pwm_device *pwm;
107 unsigned int ncfg = 0;
108 unsigned int i;
109
110 for (i = 0; i < chip->npwm; i++) {
111 pwm = &chip->pwms[i];
112 if (test_bit(PWMF_REQUESTED, &pwm->flags)) {
113 if (pwm_get_period(pwm))
114 ncfg++;
115 }
116 }
117
118 return ncfg;
119}
120
102static int sti_pwm_cmp_periods(const void *key, const void *elt) 121static int sti_pwm_cmp_periods(const void *key, const void *elt)
103{ 122{
104 unsigned long i = *(unsigned long *)key; 123 unsigned long i = *(unsigned long *)key;
@@ -124,57 +143,90 @@ static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
124{ 143{
125 struct sti_pwm_chip *pc = to_sti_pwmchip(chip); 144 struct sti_pwm_chip *pc = to_sti_pwmchip(chip);
126 struct sti_pwm_compat_data *cdata = pc->cdata; 145 struct sti_pwm_compat_data *cdata = pc->cdata;
146 struct pwm_device *cur = pc->cur;
127 struct device *dev = pc->dev; 147 struct device *dev = pc->dev;
128 unsigned int prescale, pwmvalx; 148 unsigned int prescale = 0, pwmvalx;
129 unsigned long *found; 149 unsigned long *found;
130 int ret; 150 int ret;
131 151 unsigned int ncfg;
132 /* 152 bool period_same = false;
133 * Search for matching period value. The corresponding index is our 153
134 * prescale value 154 ncfg = sti_pwm_count_configured(chip);
155 if (ncfg)
156 period_same = (period_ns == pwm_get_period(cur));
157
158 /* Allow configuration changes if one of the
159 * following conditions satisfy.
160 * 1. No channels have been configured.
161 * 2. Only one channel has been configured and the new request
162 * is for the same channel.
163 * 3. Only one channel has been configured and the new request is
164 * for a new channel and period of the new channel is same as
165 * the current configured period.
166 * 4. More than one channels are configured and period of the new
167 * requestis the same as the current period.
135 */ 168 */
136 found = bsearch(&period_ns, &pc->pwm_periods[0], 169 if (!ncfg ||
137 cdata->max_prescale + 1, sizeof(unsigned long), 170 ((ncfg == 1) && (pwm->hwpwm == cur->hwpwm)) ||
138 sti_pwm_cmp_periods); 171 ((ncfg == 1) && (pwm->hwpwm != cur->hwpwm) && period_same) ||
139 if (!found) { 172 ((ncfg > 1) && period_same)) {
140 dev_err(dev, "failed to find matching period\n"); 173 /* Enable clock before writing to PWM registers. */
174 ret = clk_enable(pc->clk);
175 if (ret)
176 return ret;
177
178 if (!period_same) {
179 /*
180 * Search for matching period value.
181 * The corresponding index is our prescale value.
182 */
183 found = bsearch(&period_ns, &pc->pwm_periods[0],
184 cdata->max_prescale + 1,
185 sizeof(unsigned long),
186 sti_pwm_cmp_periods);
187 if (!found) {
188 dev_err(dev,
189 "failed to find matching period\n");
190 ret = -EINVAL;
191 goto clk_dis;
192 }
193 prescale = found - &pc->pwm_periods[0];
194
195 ret =
196 regmap_field_write(pc->prescale_low,
197 prescale & PWM_PRESCALE_LOW_MASK);
198 if (ret)
199 goto clk_dis;
200
201 ret =
202 regmap_field_write(pc->prescale_high,
203 (prescale & PWM_PRESCALE_HIGH_MASK) >> 4);
204 if (ret)
205 goto clk_dis;
206 }
207
208 /*
209 * When PWMVal == 0, PWM pulse = 1 local clock cycle.
210 * When PWMVal == max_pwm_count,
211 * PWM pulse = (max_pwm_count + 1) local cycles,
212 * that is continuous pulse: signal never goes low.
213 */
214 pwmvalx = cdata->max_pwm_cnt * duty_ns / period_ns;
215
216 ret = regmap_write(pc->regmap, STI_DS_REG(pwm->hwpwm), pwmvalx);
217 if (ret)
218 goto clk_dis;
219
220 ret = regmap_field_write(pc->pwm_int_en, 0);
221
222 pc->cur = pwm;
223
224 dev_dbg(dev, "prescale:%u, period:%i, duty:%i, pwmvalx:%u\n",
225 prescale, period_ns, duty_ns, pwmvalx);
226 } else {
141 return -EINVAL; 227 return -EINVAL;
142 } 228 }
143 229
144 prescale = found - &pc->pwm_periods[0];
145
146 /*
147 * When PWMVal == 0, PWM pulse = 1 local clock cycle.
148 * When PWMVal == max_pwm_count,
149 * PWM pulse = (max_pwm_count + 1) local cycles,
150 * that is continuous pulse: signal never goes low.
151 */
152 pwmvalx = cdata->max_pwm_cnt * duty_ns / period_ns;
153
154 dev_dbg(dev, "prescale:%u, period:%i, duty:%i, pwmvalx:%u\n",
155 prescale, period_ns, duty_ns, pwmvalx);
156
157 /* Enable clock before writing to PWM registers */
158 ret = clk_enable(pc->clk);
159 if (ret)
160 return ret;
161
162 ret = regmap_field_write(pc->prescale_low,
163 prescale & PWM_PRESCALE_LOW_MASK);
164 if (ret)
165 goto clk_dis;
166
167 ret = regmap_field_write(pc->prescale_high,
168 (prescale & PWM_PRESCALE_HIGH_MASK) >> 4);
169 if (ret)
170 goto clk_dis;
171
172 ret = regmap_write(pc->regmap, STI_PWMVAL(pwm->hwpwm), pwmvalx);
173 if (ret)
174 goto clk_dis;
175
176 ret = regmap_field_write(pc->pwm_int_en, 0);
177
178clk_dis: 230clk_dis:
179 clk_disable(pc->clk); 231 clk_disable(pc->clk);
180 return ret; 232 return ret;