Commit 2e0f8822d1a6d839e66786e99ce1043e4ad1cd72

Authored by Rafael J. Wysocki
1 parent f58b082aed

ACPI / LPSS: Add support for exposing LTR registers to user space

Devices on the Intel Lynxpoint Low Power Subsystem (LPSS) have
registers providing access to LTR (Latency Tolerance Reporting)
functionality that allows software to monitor and possibly influence
the aggressiveness of the platform's active-state power management.

For each LPSS device, there are two modes of operation related to LTR,
the auto mode and the software mode.  In the auto mode the LTR is
set up by the platform firmware and managed by hardware.  Software
can only read the LTR register values to monitor the platform's
behavior.  In the software mode it is possible to use LTR to control
the extent to which the platform will use its built-in power
management features.

This changeset adds support for reading the LPSS devices' LTR
registers and exposing their values to user space for monitoring and
diagnostics purposes.  It re-uses the MMIO mappings created to access
the LPSS devices' clock registers for reading the values of the LTR
registers and exposes them to user space through sysfs device
attributes.  Namely, a new atrribute group, lpss_ltr, is created for
each LPSS device.  It contains three new attributes: ltr_mode,
auto_ltr, sw_ltr.  The value of the ltr_mode attribute reflects the
LTR mode being used at the moment (software vs auto) and the other
two contain the actual register values (raw) whose meaning depends
on the LTR mode.  All of these attributes are read-only.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

Showing 2 changed files with 178 additions and 5 deletions Inline Diff

Documentation/ABI/testing/sysfs-devices-lpss_ltr
File was created 1 What: /sys/devices/.../lpss_ltr/
2 Date: March 2013
3 Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
4 Description:
5 The /sys/devices/.../lpss_ltr/ directory is only present for
6 devices included into the Intel Lynxpoint Low Power Subsystem
7 (LPSS). If present, it contains attributes containing the LTR
8 mode and the values of LTR registers of the device.
9
10 What: /sys/devices/.../lpss_ltr/ltr_mode
11 Date: March 2013
12 Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
13 Description:
14 The /sys/devices/.../lpss_ltr/ltr_mode attribute contains an
15 integer number (0 or 1) indicating whether or not the devices'
16 LTR functionality is working in the software mode (1).
17
18 This attribute is read-only. If the device's runtime PM status
19 is not "active", attempts to read from this attribute cause
20 -EAGAIN to be returned.
21
22 What: /sys/devices/.../lpss_ltr/auto_ltr
23 Date: March 2013
24 Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
25 Description:
26 The /sys/devices/.../lpss_ltr/auto_ltr attribute contains the
27 current value of the device's AUTO_LTR register (raw)
28 represented as an 8-digit hexadecimal number.
29
30 This attribute is read-only. If the device's runtime PM status
31 is not "active", attempts to read from this attribute cause
32 -EAGAIN to be returned.
33
34 What: /sys/devices/.../lpss_ltr/sw_ltr
35 Date: March 2013
36 Contact: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
37 Description:
38 The /sys/devices/.../lpss_ltr/auto_ltr attribute contains the
39 current value of the device's SW_LTR register (raw) represented
40 as an 8-digit hexadecimal number.
41
42 This attribute is read-only. If the device's runtime PM status
43 is not "active", attempts to read from this attribute cause
44 -EAGAIN to be returned.
45
drivers/acpi/acpi_lpss.c
1 /* 1 /*
2 * ACPI support for Intel Lynxpoint LPSS. 2 * ACPI support for Intel Lynxpoint LPSS.
3 * 3 *
4 * Copyright (C) 2013, Intel Corporation 4 * Copyright (C) 2013, Intel Corporation
5 * Authors: Mika Westerberg <mika.westerberg@linux.intel.com> 5 * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
6 * Rafael J. Wysocki <rafael.j.wysocki@intel.com> 6 * Rafael J. Wysocki <rafael.j.wysocki@intel.com>
7 * 7 *
8 * This program is free software; you can redistribute it and/or modify 8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as 9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation. 10 * published by the Free Software Foundation.
11 */ 11 */
12 12
13 #include <linux/acpi.h> 13 #include <linux/acpi.h>
14 #include <linux/clk.h> 14 #include <linux/clk.h>
15 #include <linux/clkdev.h> 15 #include <linux/clkdev.h>
16 #include <linux/clk-provider.h> 16 #include <linux/clk-provider.h>
17 #include <linux/err.h> 17 #include <linux/err.h>
18 #include <linux/io.h> 18 #include <linux/io.h>
19 #include <linux/platform_device.h> 19 #include <linux/platform_device.h>
20 #include <linux/platform_data/clk-lpss.h> 20 #include <linux/platform_data/clk-lpss.h>
21 #include <linux/pm_runtime.h>
21 22
22 #include "internal.h" 23 #include "internal.h"
23 24
24 ACPI_MODULE_NAME("acpi_lpss"); 25 ACPI_MODULE_NAME("acpi_lpss");
25 26
26 #define LPSS_CLK_OFFSET 0x800
27 #define LPSS_CLK_SIZE 0x04 27 #define LPSS_CLK_SIZE 0x04
28 #define LPSS_LTR_SIZE 0x18
28 29
30 /* Offsets relative to LPSS_PRIVATE_OFFSET */
31 #define LPSS_GENERAL 0x08
32 #define LPSS_GENERAL_LTR_MODE_SW BIT(2)
33 #define LPSS_SW_LTR 0x10
34 #define LPSS_AUTO_LTR 0x14
35
29 struct lpss_device_desc { 36 struct lpss_device_desc {
30 bool clk_required; 37 bool clk_required;
31 const char *clk_parent; 38 const char *clk_parent;
39 bool ltr_required;
40 unsigned int prv_offset;
32 }; 41 };
33 42
34 struct lpss_private_data { 43 struct lpss_private_data {
35 void __iomem *mmio_base; 44 void __iomem *mmio_base;
36 resource_size_t mmio_size; 45 resource_size_t mmio_size;
37 struct clk *clk; 46 struct clk *clk;
38 const struct lpss_device_desc *dev_desc; 47 const struct lpss_device_desc *dev_desc;
39 }; 48 };
40 49
41 static struct lpss_device_desc lpt_dev_desc = { 50 static struct lpss_device_desc lpt_dev_desc = {
42 .clk_required = true, 51 .clk_required = true,
43 .clk_parent = "lpss_clk", 52 .clk_parent = "lpss_clk",
53 .prv_offset = 0x800,
54 .ltr_required = true,
44 }; 55 };
45 56
57 static struct lpss_device_desc lpt_sdio_dev_desc = {
58 .prv_offset = 0x1000,
59 .ltr_required = true,
60 };
61
46 static const struct acpi_device_id acpi_lpss_device_ids[] = { 62 static const struct acpi_device_id acpi_lpss_device_ids[] = {
47 /* Lynxpoint LPSS devices */ 63 /* Lynxpoint LPSS devices */
48 { "INT33C0", (unsigned long)&lpt_dev_desc }, 64 { "INT33C0", (unsigned long)&lpt_dev_desc },
49 { "INT33C1", (unsigned long)&lpt_dev_desc }, 65 { "INT33C1", (unsigned long)&lpt_dev_desc },
50 { "INT33C2", (unsigned long)&lpt_dev_desc }, 66 { "INT33C2", (unsigned long)&lpt_dev_desc },
51 { "INT33C3", (unsigned long)&lpt_dev_desc }, 67 { "INT33C3", (unsigned long)&lpt_dev_desc },
52 { "INT33C4", (unsigned long)&lpt_dev_desc }, 68 { "INT33C4", (unsigned long)&lpt_dev_desc },
53 { "INT33C5", (unsigned long)&lpt_dev_desc }, 69 { "INT33C5", (unsigned long)&lpt_dev_desc },
54 { "INT33C6", }, 70 { "INT33C6", (unsigned long)&lpt_sdio_dev_desc },
55 { "INT33C7", }, 71 { "INT33C7", },
56 72
57 { } 73 { }
58 }; 74 };
59 75
60 static int is_memory(struct acpi_resource *res, void *not_used) 76 static int is_memory(struct acpi_resource *res, void *not_used)
61 { 77 {
62 struct resource r; 78 struct resource r;
63 return !acpi_dev_resource_memory(res, &r); 79 return !acpi_dev_resource_memory(res, &r);
64 } 80 }
65 81
66 /* LPSS main clock device. */ 82 /* LPSS main clock device. */
67 static struct platform_device *lpss_clk_dev; 83 static struct platform_device *lpss_clk_dev;
68 84
69 static inline void lpt_register_clock_device(void) 85 static inline void lpt_register_clock_device(void)
70 { 86 {
71 lpss_clk_dev = platform_device_register_simple("clk-lpt", -1, NULL, 0); 87 lpss_clk_dev = platform_device_register_simple("clk-lpt", -1, NULL, 0);
72 } 88 }
73 89
74 static int register_device_clock(struct acpi_device *adev, 90 static int register_device_clock(struct acpi_device *adev,
75 struct lpss_private_data *pdata) 91 struct lpss_private_data *pdata)
76 { 92 {
77 const struct lpss_device_desc *dev_desc = pdata->dev_desc; 93 const struct lpss_device_desc *dev_desc = pdata->dev_desc;
78 94
79 if (!lpss_clk_dev) 95 if (!lpss_clk_dev)
80 lpt_register_clock_device(); 96 lpt_register_clock_device();
81 97
82 if (!dev_desc->clk_parent || !pdata->mmio_base 98 if (!dev_desc->clk_parent || !pdata->mmio_base
83 || pdata->mmio_size < LPSS_CLK_OFFSET + LPSS_CLK_SIZE) 99 || pdata->mmio_size < dev_desc->prv_offset + LPSS_CLK_SIZE)
84 return -ENODATA; 100 return -ENODATA;
85 101
86 pdata->clk = clk_register_gate(NULL, dev_name(&adev->dev), 102 pdata->clk = clk_register_gate(NULL, dev_name(&adev->dev),
87 dev_desc->clk_parent, 0, 103 dev_desc->clk_parent, 0,
88 pdata->mmio_base + LPSS_CLK_OFFSET, 104 pdata->mmio_base + dev_desc->prv_offset,
89 0, 0, NULL); 105 0, 0, NULL);
90 if (IS_ERR(pdata->clk)) 106 if (IS_ERR(pdata->clk))
91 return PTR_ERR(pdata->clk); 107 return PTR_ERR(pdata->clk);
92 108
93 clk_register_clkdev(pdata->clk, NULL, dev_name(&adev->dev)); 109 clk_register_clkdev(pdata->clk, NULL, dev_name(&adev->dev));
94 return 0; 110 return 0;
95 } 111 }
96 112
97 static int acpi_lpss_create_device(struct acpi_device *adev, 113 static int acpi_lpss_create_device(struct acpi_device *adev,
98 const struct acpi_device_id *id) 114 const struct acpi_device_id *id)
99 { 115 {
100 struct lpss_device_desc *dev_desc; 116 struct lpss_device_desc *dev_desc;
101 struct lpss_private_data *pdata; 117 struct lpss_private_data *pdata;
102 struct resource_list_entry *rentry; 118 struct resource_list_entry *rentry;
103 struct list_head resource_list; 119 struct list_head resource_list;
104 int ret; 120 int ret;
105 121
106 dev_desc = (struct lpss_device_desc *)id->driver_data; 122 dev_desc = (struct lpss_device_desc *)id->driver_data;
107 if (!dev_desc) 123 if (!dev_desc)
108 return acpi_create_platform_device(adev, id); 124 return acpi_create_platform_device(adev, id);
109 125
110 pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); 126 pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
111 if (!pdata) 127 if (!pdata)
112 return -ENOMEM; 128 return -ENOMEM;
113 129
114 INIT_LIST_HEAD(&resource_list); 130 INIT_LIST_HEAD(&resource_list);
115 ret = acpi_dev_get_resources(adev, &resource_list, is_memory, NULL); 131 ret = acpi_dev_get_resources(adev, &resource_list, is_memory, NULL);
116 if (ret < 0) 132 if (ret < 0)
117 goto err_out; 133 goto err_out;
118 134
119 list_for_each_entry(rentry, &resource_list, node) 135 list_for_each_entry(rentry, &resource_list, node)
120 if (resource_type(&rentry->res) == IORESOURCE_MEM) { 136 if (resource_type(&rentry->res) == IORESOURCE_MEM) {
121 pdata->mmio_size = resource_size(&rentry->res); 137 pdata->mmio_size = resource_size(&rentry->res);
122 pdata->mmio_base = ioremap(rentry->res.start, 138 pdata->mmio_base = ioremap(rentry->res.start,
123 pdata->mmio_size); 139 pdata->mmio_size);
124 pdata->dev_desc = dev_desc; 140 pdata->dev_desc = dev_desc;
125 break; 141 break;
126 } 142 }
127 143
128 acpi_dev_free_resource_list(&resource_list); 144 acpi_dev_free_resource_list(&resource_list);
129 145
130 if (dev_desc->clk_required) { 146 if (dev_desc->clk_required) {
131 ret = register_device_clock(adev, pdata); 147 ret = register_device_clock(adev, pdata);
132 if (ret) { 148 if (ret) {
133 /* 149 /*
134 * Skip the device, but don't terminate the namespace 150 * Skip the device, but don't terminate the namespace
135 * scan. 151 * scan.
136 */ 152 */
137 ret = 0; 153 ret = 0;
138 goto err_out; 154 goto err_out;
139 } 155 }
140 } 156 }
141 157
142 adev->driver_data = pdata; 158 adev->driver_data = pdata;
143 ret = acpi_create_platform_device(adev, id); 159 ret = acpi_create_platform_device(adev, id);
144 if (ret > 0) 160 if (ret > 0)
145 return ret; 161 return ret;
146 162
147 adev->driver_data = NULL; 163 adev->driver_data = NULL;
148 164
149 err_out: 165 err_out:
150 kfree(pdata); 166 kfree(pdata);
151 return ret; 167 return ret;
152 } 168 }
153 169
170 static int lpss_reg_read(struct device *dev, unsigned int reg, u32 *val)
171 {
172 struct acpi_device *adev;
173 struct lpss_private_data *pdata;
174 unsigned long flags;
175 int ret;
176
177 ret = acpi_bus_get_device(ACPI_HANDLE(dev), &adev);
178 if (WARN_ON(ret))
179 return ret;
180
181 spin_lock_irqsave(&dev->power.lock, flags);
182 if (pm_runtime_suspended(dev)) {
183 ret = -EAGAIN;
184 goto out;
185 }
186 pdata = acpi_driver_data(adev);
187 if (WARN_ON(!pdata || !pdata->mmio_base)) {
188 ret = -ENODEV;
189 goto out;
190 }
191 *val = readl(pdata->mmio_base + pdata->dev_desc->prv_offset + reg);
192
193 out:
194 spin_unlock_irqrestore(&dev->power.lock, flags);
195 return ret;
196 }
197
198 static ssize_t lpss_ltr_show(struct device *dev, struct device_attribute *attr,
199 char *buf)
200 {
201 u32 ltr_value = 0;
202 unsigned int reg;
203 int ret;
204
205 reg = strcmp(attr->attr.name, "auto_ltr") ? LPSS_SW_LTR : LPSS_AUTO_LTR;
206 ret = lpss_reg_read(dev, reg, &ltr_value);
207 if (ret)
208 return ret;
209
210 return snprintf(buf, PAGE_SIZE, "%08x\n", ltr_value);
211 }
212
213 static ssize_t lpss_ltr_mode_show(struct device *dev,
214 struct device_attribute *attr, char *buf)
215 {
216 u32 ltr_mode = 0;
217 char *outstr;
218 int ret;
219
220 ret = lpss_reg_read(dev, LPSS_GENERAL, &ltr_mode);
221 if (ret)
222 return ret;
223
224 outstr = (ltr_mode & LPSS_GENERAL_LTR_MODE_SW) ? "sw" : "auto";
225 return sprintf(buf, "%s\n", outstr);
226 }
227
228 static DEVICE_ATTR(auto_ltr, S_IRUSR, lpss_ltr_show, NULL);
229 static DEVICE_ATTR(sw_ltr, S_IRUSR, lpss_ltr_show, NULL);
230 static DEVICE_ATTR(ltr_mode, S_IRUSR, lpss_ltr_mode_show, NULL);
231
232 static struct attribute *lpss_attrs[] = {
233 &dev_attr_auto_ltr.attr,
234 &dev_attr_sw_ltr.attr,
235 &dev_attr_ltr_mode.attr,
236 NULL,
237 };
238
239 static struct attribute_group lpss_attr_group = {
240 .attrs = lpss_attrs,
241 .name = "lpss_ltr",
242 };
243
244 static int acpi_lpss_platform_notify(struct notifier_block *nb,
245 unsigned long action, void *data)
246 {
247 struct platform_device *pdev = to_platform_device(data);
248 struct lpss_private_data *pdata;
249 struct acpi_device *adev;
250 const struct acpi_device_id *id;
251 int ret = 0;
252
253 id = acpi_match_device(acpi_lpss_device_ids, &pdev->dev);
254 if (!id || !id->driver_data)
255 return 0;
256
257 if (acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev))
258 return 0;
259
260 pdata = acpi_driver_data(adev);
261 if (!pdata || !pdata->mmio_base || !pdata->dev_desc->ltr_required)
262 return 0;
263
264 if (pdata->mmio_size < pdata->dev_desc->prv_offset + LPSS_LTR_SIZE) {
265 dev_err(&pdev->dev, "MMIO size insufficient to access LTR\n");
266 return 0;
267 }
268
269 if (action == BUS_NOTIFY_ADD_DEVICE)
270 ret = sysfs_create_group(&pdev->dev.kobj, &lpss_attr_group);
271 else if (action == BUS_NOTIFY_DEL_DEVICE)
272 sysfs_remove_group(&pdev->dev.kobj, &lpss_attr_group);
273
274 return ret;
275 }
276
277 static struct notifier_block acpi_lpss_nb = {
278 .notifier_call = acpi_lpss_platform_notify,
279 };
280
154 static struct acpi_scan_handler lpss_handler = { 281 static struct acpi_scan_handler lpss_handler = {
155 .ids = acpi_lpss_device_ids, 282 .ids = acpi_lpss_device_ids,
156 .attach = acpi_lpss_create_device, 283 .attach = acpi_lpss_create_device,
157 }; 284 };
158 285
159 void __init acpi_lpss_init(void) 286 void __init acpi_lpss_init(void)
160 { 287 {
161 if (!lpt_clk_init()) 288 if (!lpt_clk_init()) {
289 bus_register_notifier(&platform_bus_type, &acpi_lpss_nb);
162 acpi_scan_add_handler(&lpss_handler); 290 acpi_scan_add_handler(&lpss_handler);
291 }
163 } 292 }