GNU Linux-libre 4.19.304-gnu1
[releases.git] / drivers / platform / x86 / intel_punit_ipc.c
1 /*
2  * Driver for the Intel P-Unit Mailbox IPC mechanism
3  *
4  * (C) Copyright 2015 Intel Corporation
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  *
10  * The heart of the P-Unit is the Foxton microcontroller and its firmware,
11  * which provide mailbox interface for power management usage.
12  */
13
14 #include <linux/module.h>
15 #include <linux/mod_devicetable.h>
16 #include <linux/acpi.h>
17 #include <linux/delay.h>
18 #include <linux/bitops.h>
19 #include <linux/device.h>
20 #include <linux/interrupt.h>
21 #include <linux/io.h>
22 #include <linux/platform_device.h>
23 #include <asm/intel_punit_ipc.h>
24
25 /* IPC Mailbox registers */
26 #define OFFSET_DATA_LOW         0x0
27 #define OFFSET_DATA_HIGH        0x4
28 /* bit field of interface register */
29 #define CMD_RUN                 BIT(31)
30 #define CMD_ERRCODE_MASK        GENMASK(7, 0)
31 #define CMD_PARA1_SHIFT         8
32 #define CMD_PARA2_SHIFT         16
33
34 #define CMD_TIMEOUT_SECONDS     1
35
36 enum {
37         BASE_DATA = 0,
38         BASE_IFACE,
39         BASE_MAX,
40 };
41
42 typedef struct {
43         struct device *dev;
44         struct mutex lock;
45         int irq;
46         struct completion cmd_complete;
47         /* base of interface and data registers */
48         void __iomem *base[RESERVED_IPC][BASE_MAX];
49         IPC_TYPE type;
50 } IPC_DEV;
51
52 static IPC_DEV *punit_ipcdev;
53
54 static inline u32 ipc_read_status(IPC_DEV *ipcdev, IPC_TYPE type)
55 {
56         return readl(ipcdev->base[type][BASE_IFACE]);
57 }
58
59 static inline void ipc_write_cmd(IPC_DEV *ipcdev, IPC_TYPE type, u32 cmd)
60 {
61         writel(cmd, ipcdev->base[type][BASE_IFACE]);
62 }
63
64 static inline u32 ipc_read_data_low(IPC_DEV *ipcdev, IPC_TYPE type)
65 {
66         return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW);
67 }
68
69 static inline u32 ipc_read_data_high(IPC_DEV *ipcdev, IPC_TYPE type)
70 {
71         return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH);
72 }
73
74 static inline void ipc_write_data_low(IPC_DEV *ipcdev, IPC_TYPE type, u32 data)
75 {
76         writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW);
77 }
78
79 static inline void ipc_write_data_high(IPC_DEV *ipcdev, IPC_TYPE type, u32 data)
80 {
81         writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH);
82 }
83
84 static const char *ipc_err_string(int error)
85 {
86         if (error == IPC_PUNIT_ERR_SUCCESS)
87                 return "no error";
88         else if (error == IPC_PUNIT_ERR_INVALID_CMD)
89                 return "invalid command";
90         else if (error == IPC_PUNIT_ERR_INVALID_PARAMETER)
91                 return "invalid parameter";
92         else if (error == IPC_PUNIT_ERR_CMD_TIMEOUT)
93                 return "command timeout";
94         else if (error == IPC_PUNIT_ERR_CMD_LOCKED)
95                 return "command locked";
96         else if (error == IPC_PUNIT_ERR_INVALID_VR_ID)
97                 return "invalid vr id";
98         else if (error == IPC_PUNIT_ERR_VR_ERR)
99                 return "vr error";
100         else
101                 return "unknown error";
102 }
103
104 static int intel_punit_ipc_check_status(IPC_DEV *ipcdev, IPC_TYPE type)
105 {
106         int loops = CMD_TIMEOUT_SECONDS * USEC_PER_SEC;
107         int errcode;
108         int status;
109
110         if (ipcdev->irq) {
111                 if (!wait_for_completion_timeout(&ipcdev->cmd_complete,
112                                                  CMD_TIMEOUT_SECONDS * HZ)) {
113                         dev_err(ipcdev->dev, "IPC timed out\n");
114                         return -ETIMEDOUT;
115                 }
116         } else {
117                 while ((ipc_read_status(ipcdev, type) & CMD_RUN) && --loops)
118                         udelay(1);
119                 if (!loops) {
120                         dev_err(ipcdev->dev, "IPC timed out\n");
121                         return -ETIMEDOUT;
122                 }
123         }
124
125         status = ipc_read_status(ipcdev, type);
126         errcode = status & CMD_ERRCODE_MASK;
127         if (errcode) {
128                 dev_err(ipcdev->dev, "IPC failed: %s, IPC_STS=0x%x\n",
129                         ipc_err_string(errcode), status);
130                 return -EIO;
131         }
132
133         return 0;
134 }
135
136 /**
137  * intel_punit_ipc_simple_command() - Simple IPC command
138  * @cmd:        IPC command code.
139  * @para1:      First 8bit parameter, set 0 if not used.
140  * @para2:      Second 8bit parameter, set 0 if not used.
141  *
142  * Send a IPC command to P-Unit when there is no data transaction
143  *
144  * Return:      IPC error code or 0 on success.
145  */
146 int intel_punit_ipc_simple_command(int cmd, int para1, int para2)
147 {
148         IPC_DEV *ipcdev = punit_ipcdev;
149         IPC_TYPE type;
150         u32 val;
151         int ret;
152
153         mutex_lock(&ipcdev->lock);
154
155         reinit_completion(&ipcdev->cmd_complete);
156         type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET;
157
158         val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK;
159         val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT;
160         ipc_write_cmd(ipcdev, type, val);
161         ret = intel_punit_ipc_check_status(ipcdev, type);
162
163         mutex_unlock(&ipcdev->lock);
164
165         return ret;
166 }
167 EXPORT_SYMBOL(intel_punit_ipc_simple_command);
168
169 /**
170  * intel_punit_ipc_command() - IPC command with data and pointers
171  * @cmd:        IPC command code.
172  * @para1:      First 8bit parameter, set 0 if not used.
173  * @para2:      Second 8bit parameter, set 0 if not used.
174  * @in:         Input data, 32bit for BIOS cmd, two 32bit for GTD and ISPD.
175  * @out:        Output data.
176  *
177  * Send a IPC command to P-Unit with data transaction
178  *
179  * Return:      IPC error code or 0 on success.
180  */
181 int intel_punit_ipc_command(u32 cmd, u32 para1, u32 para2, u32 *in, u32 *out)
182 {
183         IPC_DEV *ipcdev = punit_ipcdev;
184         IPC_TYPE type;
185         u32 val;
186         int ret;
187
188         mutex_lock(&ipcdev->lock);
189
190         reinit_completion(&ipcdev->cmd_complete);
191         type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET;
192
193         if (in) {
194                 ipc_write_data_low(ipcdev, type, *in);
195                 if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC)
196                         ipc_write_data_high(ipcdev, type, *++in);
197         }
198
199         val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK;
200         val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT;
201         ipc_write_cmd(ipcdev, type, val);
202
203         ret = intel_punit_ipc_check_status(ipcdev, type);
204         if (ret)
205                 goto out;
206
207         if (out) {
208                 *out = ipc_read_data_low(ipcdev, type);
209                 if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC)
210                         *++out = ipc_read_data_high(ipcdev, type);
211         }
212
213 out:
214         mutex_unlock(&ipcdev->lock);
215         return ret;
216 }
217 EXPORT_SYMBOL_GPL(intel_punit_ipc_command);
218
219 static irqreturn_t intel_punit_ioc(int irq, void *dev_id)
220 {
221         IPC_DEV *ipcdev = dev_id;
222
223         complete(&ipcdev->cmd_complete);
224         return IRQ_HANDLED;
225 }
226
227 static int intel_punit_get_bars(struct platform_device *pdev)
228 {
229         struct resource *res;
230         void __iomem *addr;
231
232         /*
233          * The following resources are required
234          * - BIOS_IPC BASE_DATA
235          * - BIOS_IPC BASE_IFACE
236          */
237         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
238         addr = devm_ioremap_resource(&pdev->dev, res);
239         if (IS_ERR(addr))
240                 return PTR_ERR(addr);
241         punit_ipcdev->base[BIOS_IPC][BASE_DATA] = addr;
242
243         res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
244         addr = devm_ioremap_resource(&pdev->dev, res);
245         if (IS_ERR(addr))
246                 return PTR_ERR(addr);
247         punit_ipcdev->base[BIOS_IPC][BASE_IFACE] = addr;
248
249         /*
250          * The following resources are optional
251          * - ISPDRIVER_IPC BASE_DATA
252          * - ISPDRIVER_IPC BASE_IFACE
253          * - GTDRIVER_IPC BASE_DATA
254          * - GTDRIVER_IPC BASE_IFACE
255          */
256         res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
257         if (res && resource_size(res) > 1) {
258                 addr = devm_ioremap_resource(&pdev->dev, res);
259                 if (!IS_ERR(addr))
260                         punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr;
261         }
262
263         res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
264         if (res && resource_size(res) > 1) {
265                 addr = devm_ioremap_resource(&pdev->dev, res);
266                 if (!IS_ERR(addr))
267                         punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr;
268         }
269
270         res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
271         if (res && resource_size(res) > 1) {
272                 addr = devm_ioremap_resource(&pdev->dev, res);
273                 if (!IS_ERR(addr))
274                         punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr;
275         }
276
277         res = platform_get_resource(pdev, IORESOURCE_MEM, 5);
278         if (res && resource_size(res) > 1) {
279                 addr = devm_ioremap_resource(&pdev->dev, res);
280                 if (!IS_ERR(addr))
281                         punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr;
282         }
283
284         return 0;
285 }
286
287 static int intel_punit_ipc_probe(struct platform_device *pdev)
288 {
289         int irq, ret;
290
291         punit_ipcdev = devm_kzalloc(&pdev->dev,
292                                     sizeof(*punit_ipcdev), GFP_KERNEL);
293         if (!punit_ipcdev)
294                 return -ENOMEM;
295
296         platform_set_drvdata(pdev, punit_ipcdev);
297
298         irq = platform_get_irq(pdev, 0);
299         if (irq < 0) {
300                 punit_ipcdev->irq = 0;
301                 dev_warn(&pdev->dev, "Invalid IRQ, using polling mode\n");
302         } else {
303                 ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc,
304                                        IRQF_NO_SUSPEND, "intel_punit_ipc",
305                                        &punit_ipcdev);
306                 if (ret) {
307                         dev_err(&pdev->dev, "Failed to request irq: %d\n", irq);
308                         return ret;
309                 }
310                 punit_ipcdev->irq = irq;
311         }
312
313         ret = intel_punit_get_bars(pdev);
314         if (ret)
315                 goto out;
316
317         punit_ipcdev->dev = &pdev->dev;
318         mutex_init(&punit_ipcdev->lock);
319         init_completion(&punit_ipcdev->cmd_complete);
320
321 out:
322         return ret;
323 }
324
325 static int intel_punit_ipc_remove(struct platform_device *pdev)
326 {
327         return 0;
328 }
329
330 static const struct acpi_device_id punit_ipc_acpi_ids[] = {
331         { "INT34D4", 0 },
332         { }
333 };
334 MODULE_DEVICE_TABLE(acpi, punit_ipc_acpi_ids);
335
336 static struct platform_driver intel_punit_ipc_driver = {
337         .probe = intel_punit_ipc_probe,
338         .remove = intel_punit_ipc_remove,
339         .driver = {
340                 .name = "intel_punit_ipc",
341                 .acpi_match_table = ACPI_PTR(punit_ipc_acpi_ids),
342         },
343 };
344
345 static int __init intel_punit_ipc_init(void)
346 {
347         return platform_driver_register(&intel_punit_ipc_driver);
348 }
349
350 static void __exit intel_punit_ipc_exit(void)
351 {
352         platform_driver_unregister(&intel_punit_ipc_driver);
353 }
354
355 MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>");
356 MODULE_DESCRIPTION("Intel P-Unit IPC driver");
357 MODULE_LICENSE("GPL v2");
358
359 /* Some modules are dependent on this, so init earlier */
360 fs_initcall(intel_punit_ipc_init);
361 module_exit(intel_punit_ipc_exit);