dma: acpi-dma: parse CSRT to extract additional resources
[linux-2.6-block.git] / drivers / acpi / acpi_lpss.c
CommitLineData
f58b082a
RW
1/*
2 * ACPI support for Intel Lynxpoint LPSS.
3 *
4 * Copyright (C) 2013, Intel Corporation
5 * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
6 * Rafael J. Wysocki <rafael.j.wysocki@intel.com>
7 *
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
10 * published by the Free Software Foundation.
11 */
12
13#include <linux/acpi.h>
14#include <linux/clk.h>
15#include <linux/clkdev.h>
16#include <linux/clk-provider.h>
17#include <linux/err.h>
18#include <linux/io.h>
19#include <linux/platform_device.h>
20#include <linux/platform_data/clk-lpss.h>
2e0f8822 21#include <linux/pm_runtime.h>
f58b082a
RW
22
23#include "internal.h"
24
25ACPI_MODULE_NAME("acpi_lpss");
26
f58b082a 27#define LPSS_CLK_SIZE 0x04
2e0f8822
RW
28#define LPSS_LTR_SIZE 0x18
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
f58b082a
RW
35
36struct lpss_device_desc {
37 bool clk_required;
38 const char *clk_parent;
2e0f8822
RW
39 bool ltr_required;
40 unsigned int prv_offset;
f58b082a
RW
41};
42
43struct lpss_private_data {
44 void __iomem *mmio_base;
45 resource_size_t mmio_size;
46 struct clk *clk;
47 const struct lpss_device_desc *dev_desc;
48};
49
50static struct lpss_device_desc lpt_dev_desc = {
51 .clk_required = true,
52 .clk_parent = "lpss_clk",
2e0f8822
RW
53 .prv_offset = 0x800,
54 .ltr_required = true,
55};
56
57static struct lpss_device_desc lpt_sdio_dev_desc = {
58 .prv_offset = 0x1000,
59 .ltr_required = true,
f58b082a
RW
60};
61
62static const struct acpi_device_id acpi_lpss_device_ids[] = {
63 /* Lynxpoint LPSS devices */
64 { "INT33C0", (unsigned long)&lpt_dev_desc },
65 { "INT33C1", (unsigned long)&lpt_dev_desc },
66 { "INT33C2", (unsigned long)&lpt_dev_desc },
67 { "INT33C3", (unsigned long)&lpt_dev_desc },
68 { "INT33C4", (unsigned long)&lpt_dev_desc },
69 { "INT33C5", (unsigned long)&lpt_dev_desc },
2e0f8822 70 { "INT33C6", (unsigned long)&lpt_sdio_dev_desc },
f58b082a
RW
71 { "INT33C7", },
72
73 { }
74};
75
76static int is_memory(struct acpi_resource *res, void *not_used)
77{
78 struct resource r;
79 return !acpi_dev_resource_memory(res, &r);
80}
81
82/* LPSS main clock device. */
83static struct platform_device *lpss_clk_dev;
84
85static inline void lpt_register_clock_device(void)
86{
87 lpss_clk_dev = platform_device_register_simple("clk-lpt", -1, NULL, 0);
88}
89
90static int register_device_clock(struct acpi_device *adev,
91 struct lpss_private_data *pdata)
92{
93 const struct lpss_device_desc *dev_desc = pdata->dev_desc;
94
95 if (!lpss_clk_dev)
96 lpt_register_clock_device();
97
98 if (!dev_desc->clk_parent || !pdata->mmio_base
2e0f8822 99 || pdata->mmio_size < dev_desc->prv_offset + LPSS_CLK_SIZE)
f58b082a
RW
100 return -ENODATA;
101
102 pdata->clk = clk_register_gate(NULL, dev_name(&adev->dev),
103 dev_desc->clk_parent, 0,
2e0f8822 104 pdata->mmio_base + dev_desc->prv_offset,
f58b082a
RW
105 0, 0, NULL);
106 if (IS_ERR(pdata->clk))
107 return PTR_ERR(pdata->clk);
108
109 clk_register_clkdev(pdata->clk, NULL, dev_name(&adev->dev));
110 return 0;
111}
112
113static int acpi_lpss_create_device(struct acpi_device *adev,
114 const struct acpi_device_id *id)
115{
116 struct lpss_device_desc *dev_desc;
117 struct lpss_private_data *pdata;
118 struct resource_list_entry *rentry;
119 struct list_head resource_list;
120 int ret;
121
122 dev_desc = (struct lpss_device_desc *)id->driver_data;
123 if (!dev_desc)
124 return acpi_create_platform_device(adev, id);
125
126 pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
127 if (!pdata)
128 return -ENOMEM;
129
130 INIT_LIST_HEAD(&resource_list);
131 ret = acpi_dev_get_resources(adev, &resource_list, is_memory, NULL);
132 if (ret < 0)
133 goto err_out;
134
135 list_for_each_entry(rentry, &resource_list, node)
136 if (resource_type(&rentry->res) == IORESOURCE_MEM) {
137 pdata->mmio_size = resource_size(&rentry->res);
138 pdata->mmio_base = ioremap(rentry->res.start,
139 pdata->mmio_size);
140 pdata->dev_desc = dev_desc;
141 break;
142 }
143
144 acpi_dev_free_resource_list(&resource_list);
145
146 if (dev_desc->clk_required) {
147 ret = register_device_clock(adev, pdata);
148 if (ret) {
149 /*
150 * Skip the device, but don't terminate the namespace
151 * scan.
152 */
cf8df962
AS
153 kfree(pdata);
154 return 0;
f58b082a
RW
155 }
156 }
157
158 adev->driver_data = pdata;
159 ret = acpi_create_platform_device(adev, id);
160 if (ret > 0)
161 return ret;
162
163 adev->driver_data = NULL;
164
165 err_out:
166 kfree(pdata);
167 return ret;
168}
169
2e0f8822
RW
170static 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
198static 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
213static 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
228static DEVICE_ATTR(auto_ltr, S_IRUSR, lpss_ltr_show, NULL);
229static DEVICE_ATTR(sw_ltr, S_IRUSR, lpss_ltr_show, NULL);
230static DEVICE_ATTR(ltr_mode, S_IRUSR, lpss_ltr_mode_show, NULL);
231
232static 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
239static struct attribute_group lpss_attr_group = {
240 .attrs = lpss_attrs,
241 .name = "lpss_ltr",
242};
243
244static 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
277static struct notifier_block acpi_lpss_nb = {
278 .notifier_call = acpi_lpss_platform_notify,
279};
280
f58b082a
RW
281static struct acpi_scan_handler lpss_handler = {
282 .ids = acpi_lpss_device_ids,
283 .attach = acpi_lpss_create_device,
284};
285
286void __init acpi_lpss_init(void)
287{
2e0f8822
RW
288 if (!lpt_clk_init()) {
289 bus_register_notifier(&platform_bus_type, &acpi_lpss_nb);
f58b082a 290 acpi_scan_add_handler(&lpss_handler);
2e0f8822 291 }
f58b082a 292}