blob: 1d30b3549748dd76c0eb834634de4c06da2154fc [file] [log] [blame]
Hong Liu8eec8a12011-02-07 14:45:55 -05001/*
Andy Shevchenko48b44522017-01-19 18:39:42 +02002 * Power button driver for Intel MID platforms.
Hong Liu8eec8a12011-02-07 14:45:55 -05003 *
Andy Shevchenko1cfd3ba2017-01-19 18:39:49 +02004 * Copyright (C) 2010,2017 Intel Corp
5 *
6 * Author: Hong Liu <hong.liu@intel.com>
7 * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Hong Liu8eec8a12011-02-07 14:45:55 -05008 *
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.
Hong Liu8eec8a12011-02-07 14:45:55 -050017 */
18
Hong Liu8eec8a12011-02-07 14:45:55 -050019#include <linux/init.h>
Hong Liu8eec8a12011-02-07 14:45:55 -050020#include <linux/input.h>
Andy Shevchenko7591b9f2017-01-19 18:39:48 +020021#include <linux/interrupt.h>
Michael Demeter77145672012-01-26 17:40:27 +000022#include <linux/mfd/intel_msic.h>
Andy Shevchenko7591b9f2017-01-19 18:39:48 +020023#include <linux/module.h>
24#include <linux/platform_device.h>
Sudeep Holladaea5a62015-09-21 16:47:01 +010025#include <linux/pm_wakeirq.h>
Andy Shevchenko7591b9f2017-01-19 18:39:48 +020026#include <linux/slab.h>
Hong Liu8eec8a12011-02-07 14:45:55 -050027
Andy Shevchenko18934ec2017-01-19 18:39:43 +020028#include <asm/cpu_device_id.h>
29#include <asm/intel-family.h>
Andy Shevchenko6a0f9982017-01-19 18:39:46 +020030#include <asm/intel_scu_ipc.h>
Andy Shevchenko18934ec2017-01-19 18:39:43 +020031
Hong Liu8eec8a12011-02-07 14:45:55 -050032#define DRIVER_NAME "msic_power_btn"
33
Ameya Palandeb9e06692011-04-06 17:44:37 +030034#define MSIC_PB_LEVEL (1 << 3) /* 1 - release, 0 - press */
Hong Liu8eec8a12011-02-07 14:45:55 -050035
Michael Demeter77145672012-01-26 17:40:27 +000036/*
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
Andy Shevchenko6a0f9982017-01-19 18:39:46 +020042/* Intel Tangier */
43#define MRFLD_PBSTAT_ADDR 0xfffff61a
44#define MRFLD_PB_LEVEL (1 << 4) /* 1 - release, 0 - press */
45
46/* Basin Cove PMIC */
47#define BCOVE_PBIRQ 0x02
48#define BCOVE_IRQLVL1MSK 0x0c
49#define BCOVE_PBIRQMASK 0x0d
50
Andy Shevchenko18934ec2017-01-19 18:39:43 +020051struct mid_pb_ddata {
52 struct device *dev;
Andy Shevchenko6a0f9982017-01-19 18:39:46 +020053 void __iomem *reg;
Andy Shevchenko18934ec2017-01-19 18:39:43 +020054 int irq;
55 struct input_dev *input;
56 int (*pbstat)(struct mid_pb_ddata *ddata, int *value);
Andy Shevchenko4b819c62017-01-19 18:39:44 +020057 int (*ack)(struct mid_pb_ddata *ddata);
Andy Shevchenko6a0f9982017-01-19 18:39:46 +020058 int (*setup)(struct mid_pb_ddata *ddata);
Andy Shevchenko18934ec2017-01-19 18:39:43 +020059};
60
61static int mfld_pbstat(struct mid_pb_ddata *ddata, int *value)
Hong Liu8eec8a12011-02-07 14:45:55 -050062{
Andy Shevchenko18934ec2017-01-19 18:39:43 +020063 struct input_dev *input = ddata->input;
Hong Liu8eec8a12011-02-07 14:45:55 -050064 int ret;
65 u8 pbstat;
66
Michael Demeter77145672012-01-26 17:40:27 +000067 ret = intel_msic_reg_read(INTEL_MSIC_PBSTATUS, &pbstat);
Andy Shevchenko18934ec2017-01-19 18:39:43 +020068 if (ret)
69 return ret;
70
Michael Demeter77145672012-01-26 17:40:27 +000071 dev_dbg(input->dev.parent, "PB_INT status= %d\n", pbstat);
72
Andy Shevchenko18934ec2017-01-19 18:39:43 +020073 *value = !(pbstat & MSIC_PB_LEVEL);
74 return 0;
75}
76
Andy Shevchenko4b819c62017-01-19 18:39:44 +020077static int mfld_ack(struct mid_pb_ddata *ddata)
78{
79 /*
80 * SCU firmware might send power button interrupts to IA core before
81 * kernel boots and doesn't get EOI from IA core. The first bit of
82 * MSIC reg 0x21 is kept masked, and SCU firmware doesn't send new
83 * power interrupt to Android kernel. Unmask the bit when probing
84 * power button in kernel.
85 * There is a very narrow race between irq handler and power button
86 * initialization. The race happens rarely. So we needn't worry
87 * about it.
88 */
89 return intel_msic_reg_update(INTEL_MSIC_IRQLVL1MSK, 0, MSIC_PWRBTNM);
90}
91
Andy Shevchenko6a0f9982017-01-19 18:39:46 +020092static int mrfld_pbstat(struct mid_pb_ddata *ddata, int *value)
93{
94 struct input_dev *input = ddata->input;
95 u8 pbstat;
96
97 pbstat = readb(ddata->reg);
98
99 dev_dbg(input->dev.parent, "PB_INT status= %d\n", pbstat);
100
101 *value = !(pbstat & MRFLD_PB_LEVEL);
102 return 0;
103}
104
105static int mrfld_ack(struct mid_pb_ddata *ddata)
106{
107 return intel_scu_ipc_update_register(BCOVE_IRQLVL1MSK, 0, MSIC_PWRBTNM);
108}
109
110static int mrfld_setup(struct mid_pb_ddata *ddata)
111{
112 ddata->reg = devm_ioremap_nocache(ddata->dev, MRFLD_PBSTAT_ADDR, 1);
113 if (!ddata->reg)
114 return -ENOMEM;
115
116 /* Unmask the PBIRQ and MPBIRQ on Tangier */
117 intel_scu_ipc_update_register(BCOVE_PBIRQ, 0, MSIC_PWRBTNM);
118 intel_scu_ipc_update_register(BCOVE_PBIRQMASK, 0, MSIC_PWRBTNM);
119
120 return 0;
121}
122
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200123static irqreturn_t mid_pb_isr(int irq, void *dev_id)
124{
125 struct mid_pb_ddata *ddata = dev_id;
126 struct input_dev *input = ddata->input;
127 int value;
128 int ret;
129
130 ret = ddata->pbstat(ddata, &value);
Ameya Palandeb9e06692011-04-06 17:44:37 +0300131 if (ret < 0) {
Andy Shevchenkofdde1a822017-01-19 18:39:47 +0200132 dev_err(input->dev.parent,
133 "Read error %d while reading MSIC_PB_STATUS\n", ret);
Ameya Palandeb9e06692011-04-06 17:44:37 +0300134 } else {
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200135 input_event(input, EV_KEY, KEY_POWER, value);
Ameya Palandeb9e06692011-04-06 17:44:37 +0300136 input_sync(input);
137 }
Hong Liu8eec8a12011-02-07 14:45:55 -0500138
Andy Shevchenko553e9c12017-01-19 18:39:45 +0200139 ddata->ack(ddata);
Hong Liu8eec8a12011-02-07 14:45:55 -0500140 return IRQ_HANDLED;
141}
142
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200143static struct mid_pb_ddata mfld_ddata = {
144 .pbstat = mfld_pbstat,
Andy Shevchenko4b819c62017-01-19 18:39:44 +0200145 .ack = mfld_ack,
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200146};
147
Andy Shevchenko6a0f9982017-01-19 18:39:46 +0200148static struct mid_pb_ddata mrfld_ddata = {
149 .pbstat = mrfld_pbstat,
150 .ack = mrfld_ack,
151 .setup = mrfld_setup,
152};
153
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200154#define ICPU(model, ddata) \
155 { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, (kernel_ulong_t)&ddata }
156
157static const struct x86_cpu_id mid_pb_cpu_ids[] = {
158 ICPU(INTEL_FAM6_ATOM_PENWELL, mfld_ddata),
Andy Shevchenko6a0f9982017-01-19 18:39:46 +0200159 ICPU(INTEL_FAM6_ATOM_MERRIFIELD, mrfld_ddata),
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200160 {}
161};
162
Andy Shevchenko48b44522017-01-19 18:39:42 +0200163static int mid_pb_probe(struct platform_device *pdev)
Hong Liu8eec8a12011-02-07 14:45:55 -0500164{
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200165 const struct x86_cpu_id *id;
166 struct mid_pb_ddata *ddata;
Hong Liu8eec8a12011-02-07 14:45:55 -0500167 struct input_dev *input;
Ameya Palandeb9e06692011-04-06 17:44:37 +0300168 int irq = platform_get_irq(pdev, 0);
Hong Liu8eec8a12011-02-07 14:45:55 -0500169 int error;
170
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200171 id = x86_match_cpu(mid_pb_cpu_ids);
172 if (!id)
173 return -ENODEV;
174
Hong Liu8eec8a12011-02-07 14:45:55 -0500175 if (irq < 0)
176 return -EINVAL;
177
Andy Shevchenko07d90892017-01-19 18:39:41 +0200178 input = devm_input_allocate_device(&pdev->dev);
Joe Perchesb222cca2013-10-23 12:14:52 -0700179 if (!input)
Ameya Palandeb9e06692011-04-06 17:44:37 +0300180 return -ENOMEM;
Hong Liu8eec8a12011-02-07 14:45:55 -0500181
Hong Liu8eec8a12011-02-07 14:45:55 -0500182 input->name = pdev->name;
183 input->phys = "power-button/input0";
184 input->id.bustype = BUS_HOST;
185 input->dev.parent = &pdev->dev;
186
187 input_set_capability(input, EV_KEY, KEY_POWER);
188
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200189 ddata = (struct mid_pb_ddata *)id->driver_data;
190 if (!ddata)
191 return -ENODATA;
192
193 ddata->dev = &pdev->dev;
194 ddata->irq = irq;
195 ddata->input = input;
196
Andy Shevchenko6a0f9982017-01-19 18:39:46 +0200197 if (ddata->setup) {
198 error = ddata->setup(ddata);
199 if (error)
200 return error;
201 }
202
Andy Shevchenko48b44522017-01-19 18:39:42 +0200203 error = devm_request_threaded_irq(&pdev->dev, irq, NULL, mid_pb_isr,
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200204 IRQF_ONESHOT, DRIVER_NAME, ddata);
Hong Liu8eec8a12011-02-07 14:45:55 -0500205 if (error) {
Andy Shevchenkofdde1a822017-01-19 18:39:47 +0200206 dev_err(&pdev->dev,
207 "Unable to request irq %d for MID power button\n", irq);
Andy Shevchenko07d90892017-01-19 18:39:41 +0200208 return error;
Hong Liu8eec8a12011-02-07 14:45:55 -0500209 }
210
211 error = input_register_device(input);
212 if (error) {
Andy Shevchenkofdde1a822017-01-19 18:39:47 +0200213 dev_err(&pdev->dev,
214 "Unable to register input dev, error %d\n", error);
Andy Shevchenko07d90892017-01-19 18:39:41 +0200215 return error;
Hong Liu8eec8a12011-02-07 14:45:55 -0500216 }
217
Andy Shevchenko18934ec2017-01-19 18:39:43 +0200218 platform_set_drvdata(pdev, ddata);
Michael Demeter77145672012-01-26 17:40:27 +0000219
Andy Shevchenko4b819c62017-01-19 18:39:44 +0200220 error = ddata->ack(ddata);
Michael Demeter77145672012-01-26 17:40:27 +0000221 if (error) {
Andy Shevchenkofdde1a822017-01-19 18:39:47 +0200222 dev_err(&pdev->dev,
223 "Unable to clear power button interrupt, error: %d\n",
224 error);
Andy Shevchenko07d90892017-01-19 18:39:41 +0200225 return error;
Michael Demeter77145672012-01-26 17:40:27 +0000226 }
227
Andy Shevchenko07d90892017-01-19 18:39:41 +0200228 device_init_wakeup(&pdev->dev, true);
229 dev_pm_set_wake_irq(&pdev->dev, irq);
Hong Liu8eec8a12011-02-07 14:45:55 -0500230
Andy Shevchenko07d90892017-01-19 18:39:41 +0200231 return 0;
Hong Liu8eec8a12011-02-07 14:45:55 -0500232}
233
Andy Shevchenko48b44522017-01-19 18:39:42 +0200234static int mid_pb_remove(struct platform_device *pdev)
Hong Liu8eec8a12011-02-07 14:45:55 -0500235{
Sudeep Holladaea5a62015-09-21 16:47:01 +0100236 dev_pm_clear_wake_irq(&pdev->dev);
237 device_init_wakeup(&pdev->dev, false);
Ameya Palandeb9e06692011-04-06 17:44:37 +0300238
Hong Liu8eec8a12011-02-07 14:45:55 -0500239 return 0;
240}
241
Andy Shevchenko48b44522017-01-19 18:39:42 +0200242static struct platform_driver mid_pb_driver = {
Hong Liu8eec8a12011-02-07 14:45:55 -0500243 .driver = {
244 .name = DRIVER_NAME,
Hong Liu8eec8a12011-02-07 14:45:55 -0500245 },
Andy Shevchenko48b44522017-01-19 18:39:42 +0200246 .probe = mid_pb_probe,
247 .remove = mid_pb_remove,
Hong Liu8eec8a12011-02-07 14:45:55 -0500248};
249
Andy Shevchenko48b44522017-01-19 18:39:42 +0200250module_platform_driver(mid_pb_driver);
Hong Liu8eec8a12011-02-07 14:45:55 -0500251
252MODULE_AUTHOR("Hong Liu <hong.liu@intel.com>");
Andy Shevchenko48b44522017-01-19 18:39:42 +0200253MODULE_DESCRIPTION("Intel MID Power Button Driver");
Hong Liu8eec8a12011-02-07 14:45:55 -0500254MODULE_LICENSE("GPL v2");
255MODULE_ALIAS("platform:" DRIVER_NAME);