diff options
author | Ben Widawsky <ben@bwidawsk.net> | 2012-09-12 21:12:07 -0400 |
---|---|---|
committer | Daniel Vetter <daniel.vetter@ffwll.ch> | 2012-09-20 08:23:06 -0400 |
commit | 46ddf194776500e132693dd2c11b28a971070cc0 (patch) | |
tree | c216e4694d15393519ca5e39bca026b81563a5dc | |
parent | b6f69c9a7f800c9f22cc94aa193f8451b3cc299c (diff) |
drm/i915: Add setters for min/max frequency
Provide a standardized sysfs interface for setting min, and max
frequencies. The code which reads the limits were lifted from the
debugfs files. As a brief explanation, the limits are similar to the CPU
p-states. We have 3 states:
RP0 - ie. max frequency
RP1 - ie. "preferred min" frequency
RPn - seriously lowest frequency
Initially Daniel asked me to clamp the writes to supported values, but
in conforming to the way the cpufreq drivers seem to work, instead
return -EINVAL (noticed by Jesse in discussion).
The values can be used by userspace wishing to control the limits of the
GPU (see the CC list for people who care).
v4: Make exceeding the soft limits return -EINVAL as well (Daniel)
v3: bug fix (Ben) - was passing the MHz value to gen6_set_rps instead of
the step value. To fix, deal only with step values by doing the divide
at the top.
v2: add the dropped mutex_unlock in error cases (Chris)
EINVAL on both too min, or too max (Daniel)
v2 Reviewed-by: Chris Wilson <chris@chris-wilson.co.uk>
CC: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Ben Widawsky <ben@bwidawsk.net>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
-rw-r--r-- | drivers/gpu/drm/i915/i915_sysfs.c | 83 |
1 files changed, 81 insertions, 2 deletions
diff --git a/drivers/gpu/drm/i915/i915_sysfs.c b/drivers/gpu/drm/i915/i915_sysfs.c index 1da07e3e85af..1ffb9880d64c 100644 --- a/drivers/gpu/drm/i915/i915_sysfs.c +++ b/drivers/gpu/drm/i915/i915_sysfs.c | |||
@@ -238,6 +238,45 @@ static ssize_t gt_max_freq_mhz_show(struct device *kdev, struct device_attribute | |||
238 | return snprintf(buf, PAGE_SIZE, "%d", ret); | 238 | return snprintf(buf, PAGE_SIZE, "%d", ret); |
239 | } | 239 | } |
240 | 240 | ||
241 | static ssize_t gt_max_freq_mhz_store(struct device *kdev, | ||
242 | struct device_attribute *attr, | ||
243 | const char *buf, size_t count) | ||
244 | { | ||
245 | struct drm_minor *minor = container_of(kdev, struct drm_minor, kdev); | ||
246 | struct drm_device *dev = minor->dev; | ||
247 | struct drm_i915_private *dev_priv = dev->dev_private; | ||
248 | u32 val, rp_state_cap, hw_max, hw_min; | ||
249 | ssize_t ret; | ||
250 | |||
251 | ret = kstrtou32(buf, 0, &val); | ||
252 | if (ret) | ||
253 | return ret; | ||
254 | |||
255 | val /= GT_FREQUENCY_MULTIPLIER; | ||
256 | |||
257 | ret = mutex_lock_interruptible(&dev->struct_mutex); | ||
258 | if (ret) | ||
259 | return ret; | ||
260 | |||
261 | rp_state_cap = I915_READ(GEN6_RP_STATE_CAP); | ||
262 | hw_max = (rp_state_cap & 0xff); | ||
263 | hw_min = ((rp_state_cap & 0xff0000) >> 16); | ||
264 | |||
265 | if (val < hw_min || val > hw_max || val < dev_priv->rps.min_delay) { | ||
266 | mutex_unlock(&dev->struct_mutex); | ||
267 | return -EINVAL; | ||
268 | } | ||
269 | |||
270 | if (dev_priv->rps.cur_delay > val) | ||
271 | gen6_set_rps(dev_priv->dev, val); | ||
272 | |||
273 | dev_priv->rps.max_delay = val; | ||
274 | |||
275 | mutex_unlock(&dev->struct_mutex); | ||
276 | |||
277 | return count; | ||
278 | } | ||
279 | |||
241 | static ssize_t gt_min_freq_mhz_show(struct device *kdev, struct device_attribute *attr, char *buf) | 280 | static ssize_t gt_min_freq_mhz_show(struct device *kdev, struct device_attribute *attr, char *buf) |
242 | { | 281 | { |
243 | struct drm_minor *minor = container_of(kdev, struct drm_minor, kdev); | 282 | struct drm_minor *minor = container_of(kdev, struct drm_minor, kdev); |
@@ -255,9 +294,49 @@ static ssize_t gt_min_freq_mhz_show(struct device *kdev, struct device_attribute | |||
255 | return snprintf(buf, PAGE_SIZE, "%d", ret); | 294 | return snprintf(buf, PAGE_SIZE, "%d", ret); |
256 | } | 295 | } |
257 | 296 | ||
297 | static ssize_t gt_min_freq_mhz_store(struct device *kdev, | ||
298 | struct device_attribute *attr, | ||
299 | const char *buf, size_t count) | ||
300 | { | ||
301 | struct drm_minor *minor = container_of(kdev, struct drm_minor, kdev); | ||
302 | struct drm_device *dev = minor->dev; | ||
303 | struct drm_i915_private *dev_priv = dev->dev_private; | ||
304 | u32 val, rp_state_cap, hw_max, hw_min; | ||
305 | ssize_t ret; | ||
306 | |||
307 | ret = kstrtou32(buf, 0, &val); | ||
308 | if (ret) | ||
309 | return ret; | ||
310 | |||
311 | val /= GT_FREQUENCY_MULTIPLIER; | ||
312 | |||
313 | ret = mutex_lock_interruptible(&dev->struct_mutex); | ||
314 | if (ret) | ||
315 | return ret; | ||
316 | |||
317 | rp_state_cap = I915_READ(GEN6_RP_STATE_CAP); | ||
318 | hw_max = (rp_state_cap & 0xff); | ||
319 | hw_min = ((rp_state_cap & 0xff0000) >> 16); | ||
320 | |||
321 | if (val < hw_min || val > hw_max || val > dev_priv->rps.max_delay) { | ||
322 | mutex_unlock(&dev->struct_mutex); | ||
323 | return -EINVAL; | ||
324 | } | ||
325 | |||
326 | if (dev_priv->rps.cur_delay < val) | ||
327 | gen6_set_rps(dev_priv->dev, val); | ||
328 | |||
329 | dev_priv->rps.min_delay = val; | ||
330 | |||
331 | mutex_unlock(&dev->struct_mutex); | ||
332 | |||
333 | return count; | ||
334 | |||
335 | } | ||
336 | |||
258 | static DEVICE_ATTR(gt_cur_freq_mhz, S_IRUGO, gt_cur_freq_mhz_show, NULL); | 337 | static DEVICE_ATTR(gt_cur_freq_mhz, S_IRUGO, gt_cur_freq_mhz_show, NULL); |
259 | static DEVICE_ATTR(gt_max_freq_mhz, S_IRUGO | S_IWUSR, gt_max_freq_mhz_show, NULL); | 338 | static DEVICE_ATTR(gt_max_freq_mhz, S_IRUGO | S_IWUSR, gt_max_freq_mhz_show, gt_max_freq_mhz_store); |
260 | static DEVICE_ATTR(gt_min_freq_mhz, S_IRUGO | S_IWUSR, gt_min_freq_mhz_show, NULL); | 339 | static DEVICE_ATTR(gt_min_freq_mhz, S_IRUGO | S_IWUSR, gt_min_freq_mhz_show, gt_min_freq_mhz_store); |
261 | 340 | ||
262 | static const struct attribute *gen6_attrs[] = { | 341 | static const struct attribute *gen6_attrs[] = { |
263 | &dev_attr_gt_cur_freq_mhz.attr, | 342 | &dev_attr_gt_cur_freq_mhz.attr, |