]>
Commit | Line | Data |
---|---|---|
8eec8a11 | 1 | /* |
48b44529 | 2 | * Power button driver for Intel MID platforms. |
8eec8a11 | 3 | * |
1cfd3ba0 AS |
4 | * Copyright (C) 2010,2017 Intel Corp |
5 | * | |
6 | * Author: Hong Liu <hong.liu@intel.com> | |
7 | * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com> | |
8eec8a11 HL |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; version 2 of the License. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but | |
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | * General Public License for more details. | |
8eec8a11 HL |
17 | */ |
18 | ||
8eec8a11 | 19 | #include <linux/init.h> |
8eec8a11 | 20 | #include <linux/input.h> |
7591b9f5 | 21 | #include <linux/interrupt.h> |
7714567c | 22 | #include <linux/mfd/intel_msic.h> |
7591b9f5 AS |
23 | #include <linux/module.h> |
24 | #include <linux/platform_device.h> | |
daea5a65 | 25 | #include <linux/pm_wakeirq.h> |
7591b9f5 | 26 | #include <linux/slab.h> |
8eec8a11 | 27 | |
18934ece AS |
28 | #include <asm/cpu_device_id.h> |
29 | #include <asm/intel-family.h> | |
6a0f9988 | 30 | #include <asm/intel_scu_ipc.h> |
18934ece | 31 | |
8eec8a11 HL |
32 | #define DRIVER_NAME "msic_power_btn" |
33 | ||
b9e06694 | 34 | #define MSIC_PB_LEVEL (1 << 3) /* 1 - release, 0 - press */ |
8eec8a11 | 35 | |
7714567c MD |
36 | /* |
37 | * MSIC document ti_datasheet defines the 1st bit reg 0x21 is used to mask | |
38 | * power button interrupt | |
39 | */ | |
40 | #define MSIC_PWRBTNM (1 << 0) | |
41 | ||
6a0f9988 | 42 | /* Intel Tangier */ |
b30f3f8e | 43 | #define BCOVE_PB_LEVEL (1 << 4) /* 1 - release, 0 - press */ |
6a0f9988 AS |
44 | |
45 | /* Basin Cove PMIC */ | |
46 | #define BCOVE_PBIRQ 0x02 | |
47 | #define BCOVE_IRQLVL1MSK 0x0c | |
48 | #define BCOVE_PBIRQMASK 0x0d | |
b30f3f8e | 49 | #define BCOVE_PBSTATUS 0x27 |
6a0f9988 | 50 | |
18934ece AS |
51 | struct mid_pb_ddata { |
52 | struct device *dev; | |
53 | int irq; | |
54 | struct input_dev *input; | |
ca45ba06 | 55 | unsigned short mirqlvl1_addr; |
b30f3f8e AS |
56 | unsigned short pbstat_addr; |
57 | u8 pbstat_mask; | |
6a0f9988 | 58 | int (*setup)(struct mid_pb_ddata *ddata); |
18934ece AS |
59 | }; |
60 | ||
b30f3f8e | 61 | static int mid_pbstat(struct mid_pb_ddata *ddata, int *value) |
8eec8a11 | 62 | { |
18934ece | 63 | struct input_dev *input = ddata->input; |
8eec8a11 HL |
64 | int ret; |
65 | u8 pbstat; | |
66 | ||
25b4a38f | 67 | ret = intel_scu_ipc_ioread8(ddata->pbstat_addr, &pbstat); |
18934ece AS |
68 | if (ret) |
69 | return ret; | |
70 | ||
7714567c MD |
71 | dev_dbg(input->dev.parent, "PB_INT status= %d\n", pbstat); |
72 | ||
b30f3f8e | 73 | *value = !(pbstat & ddata->pbstat_mask); |
18934ece AS |
74 | return 0; |
75 | } | |
76 | ||
ca45ba06 | 77 | static int mid_irq_ack(struct mid_pb_ddata *ddata) |
4b819c6d | 78 | { |
25b4a38f | 79 | return intel_scu_ipc_update_register(ddata->mirqlvl1_addr, 0, MSIC_PWRBTNM); |
6a0f9988 AS |
80 | } |
81 | ||
82 | static int mrfld_setup(struct mid_pb_ddata *ddata) | |
83 | { | |
6a0f9988 AS |
84 | /* Unmask the PBIRQ and MPBIRQ on Tangier */ |
85 | intel_scu_ipc_update_register(BCOVE_PBIRQ, 0, MSIC_PWRBTNM); | |
86 | intel_scu_ipc_update_register(BCOVE_PBIRQMASK, 0, MSIC_PWRBTNM); | |
87 | ||
88 | return 0; | |
89 | } | |
90 | ||
18934ece AS |
91 | static irqreturn_t mid_pb_isr(int irq, void *dev_id) |
92 | { | |
93 | struct mid_pb_ddata *ddata = dev_id; | |
94 | struct input_dev *input = ddata->input; | |
b30f3f8e | 95 | int value = 0; |
18934ece AS |
96 | int ret; |
97 | ||
b30f3f8e | 98 | ret = mid_pbstat(ddata, &value); |
b9e06694 | 99 | if (ret < 0) { |
fdde1a82 AS |
100 | dev_err(input->dev.parent, |
101 | "Read error %d while reading MSIC_PB_STATUS\n", ret); | |
b9e06694 | 102 | } else { |
18934ece | 103 | input_event(input, EV_KEY, KEY_POWER, value); |
b9e06694 AP |
104 | input_sync(input); |
105 | } | |
8eec8a11 | 106 | |
ca45ba06 | 107 | mid_irq_ack(ddata); |
8eec8a11 HL |
108 | return IRQ_HANDLED; |
109 | } | |
110 | ||
c94a8ff1 | 111 | static const struct mid_pb_ddata mfld_ddata = { |
ca45ba06 | 112 | .mirqlvl1_addr = INTEL_MSIC_IRQLVL1MSK, |
b30f3f8e AS |
113 | .pbstat_addr = INTEL_MSIC_PBSTATUS, |
114 | .pbstat_mask = MSIC_PB_LEVEL, | |
18934ece AS |
115 | }; |
116 | ||
c94a8ff1 | 117 | static const struct mid_pb_ddata mrfld_ddata = { |
ca45ba06 | 118 | .mirqlvl1_addr = BCOVE_IRQLVL1MSK, |
b30f3f8e AS |
119 | .pbstat_addr = BCOVE_PBSTATUS, |
120 | .pbstat_mask = BCOVE_PB_LEVEL, | |
6a0f9988 AS |
121 | .setup = mrfld_setup, |
122 | }; | |
123 | ||
18934ece AS |
124 | #define ICPU(model, ddata) \ |
125 | { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, (kernel_ulong_t)&ddata } | |
126 | ||
127 | static const struct x86_cpu_id mid_pb_cpu_ids[] = { | |
2811e5d5 PZ |
128 | ICPU(INTEL_FAM6_ATOM_SALTWELL_MID, mfld_ddata), |
129 | ICPU(INTEL_FAM6_ATOM_SILVERMONT_MID, mrfld_ddata), | |
18934ece AS |
130 | {} |
131 | }; | |
132 | ||
48b44529 | 133 | static int mid_pb_probe(struct platform_device *pdev) |
8eec8a11 | 134 | { |
18934ece AS |
135 | const struct x86_cpu_id *id; |
136 | struct mid_pb_ddata *ddata; | |
8eec8a11 | 137 | struct input_dev *input; |
b9e06694 | 138 | int irq = platform_get_irq(pdev, 0); |
8eec8a11 HL |
139 | int error; |
140 | ||
18934ece AS |
141 | id = x86_match_cpu(mid_pb_cpu_ids); |
142 | if (!id) | |
143 | return -ENODEV; | |
144 | ||
fe4e8d09 GS |
145 | if (irq < 0) { |
146 | dev_err(&pdev->dev, "Failed to get IRQ: %d\n", irq); | |
147 | return irq; | |
148 | } | |
8eec8a11 | 149 | |
07d9089d | 150 | input = devm_input_allocate_device(&pdev->dev); |
b222cca6 | 151 | if (!input) |
b9e06694 | 152 | return -ENOMEM; |
8eec8a11 | 153 | |
8eec8a11 HL |
154 | input->name = pdev->name; |
155 | input->phys = "power-button/input0"; | |
156 | input->id.bustype = BUS_HOST; | |
157 | input->dev.parent = &pdev->dev; | |
158 | ||
159 | input_set_capability(input, EV_KEY, KEY_POWER); | |
160 | ||
18934ece AS |
161 | ddata = (struct mid_pb_ddata *)id->driver_data; |
162 | if (!ddata) | |
163 | return -ENODATA; | |
164 | ||
165 | ddata->dev = &pdev->dev; | |
166 | ddata->irq = irq; | |
167 | ddata->input = input; | |
168 | ||
6a0f9988 AS |
169 | if (ddata->setup) { |
170 | error = ddata->setup(ddata); | |
171 | if (error) | |
172 | return error; | |
173 | } | |
174 | ||
48b44529 | 175 | error = devm_request_threaded_irq(&pdev->dev, irq, NULL, mid_pb_isr, |
18934ece | 176 | IRQF_ONESHOT, DRIVER_NAME, ddata); |
8eec8a11 | 177 | if (error) { |
fdde1a82 AS |
178 | dev_err(&pdev->dev, |
179 | "Unable to request irq %d for MID power button\n", irq); | |
07d9089d | 180 | return error; |
8eec8a11 HL |
181 | } |
182 | ||
183 | error = input_register_device(input); | |
184 | if (error) { | |
fdde1a82 AS |
185 | dev_err(&pdev->dev, |
186 | "Unable to register input dev, error %d\n", error); | |
07d9089d | 187 | return error; |
8eec8a11 HL |
188 | } |
189 | ||
18934ece | 190 | platform_set_drvdata(pdev, ddata); |
7714567c | 191 | |
5cb44ee2 AS |
192 | /* |
193 | * SCU firmware might send power button interrupts to IA core before | |
194 | * kernel boots and doesn't get EOI from IA core. The first bit of | |
195 | * MSIC reg 0x21 is kept masked, and SCU firmware doesn't send new | |
196 | * power interrupt to Android kernel. Unmask the bit when probing | |
197 | * power button in kernel. | |
198 | * There is a very narrow race between irq handler and power button | |
199 | * initialization. The race happens rarely. So we needn't worry | |
200 | * about it. | |
201 | */ | |
ca45ba06 | 202 | error = mid_irq_ack(ddata); |
7714567c | 203 | if (error) { |
fdde1a82 AS |
204 | dev_err(&pdev->dev, |
205 | "Unable to clear power button interrupt, error: %d\n", | |
206 | error); | |
07d9089d | 207 | return error; |
7714567c MD |
208 | } |
209 | ||
07d9089d AS |
210 | device_init_wakeup(&pdev->dev, true); |
211 | dev_pm_set_wake_irq(&pdev->dev, irq); | |
8eec8a11 | 212 | |
07d9089d | 213 | return 0; |
8eec8a11 HL |
214 | } |
215 | ||
48b44529 | 216 | static int mid_pb_remove(struct platform_device *pdev) |
8eec8a11 | 217 | { |
daea5a65 SH |
218 | dev_pm_clear_wake_irq(&pdev->dev); |
219 | device_init_wakeup(&pdev->dev, false); | |
b9e06694 | 220 | |
8eec8a11 HL |
221 | return 0; |
222 | } | |
223 | ||
48b44529 | 224 | static struct platform_driver mid_pb_driver = { |
8eec8a11 HL |
225 | .driver = { |
226 | .name = DRIVER_NAME, | |
8eec8a11 | 227 | }, |
48b44529 AS |
228 | .probe = mid_pb_probe, |
229 | .remove = mid_pb_remove, | |
8eec8a11 HL |
230 | }; |
231 | ||
48b44529 | 232 | module_platform_driver(mid_pb_driver); |
8eec8a11 HL |
233 | |
234 | MODULE_AUTHOR("Hong Liu <hong.liu@intel.com>"); | |
48b44529 | 235 | MODULE_DESCRIPTION("Intel MID Power Button Driver"); |
8eec8a11 HL |
236 | MODULE_LICENSE("GPL v2"); |
237 | MODULE_ALIAS("platform:" DRIVER_NAME); |