1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Panasonic HotKey and LCD brightness control driver
4*4882a593Smuzhiyun * (C) 2004 Hiroshi Miura <miura@da-cha.org>
5*4882a593Smuzhiyun * (C) 2004 NTT DATA Intellilink Co. http://www.intellilink.co.jp/
6*4882a593Smuzhiyun * (C) YOKOTA Hiroshi <yokota (at) netlab. is. tsukuba. ac. jp>
7*4882a593Smuzhiyun * (C) 2004 David Bronaugh <dbronaugh>
8*4882a593Smuzhiyun * (C) 2006-2008 Harald Welte <laforge@gnumonks.org>
9*4882a593Smuzhiyun *
10*4882a593Smuzhiyun * derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte
11*4882a593Smuzhiyun *
12*4882a593Smuzhiyun *---------------------------------------------------------------------------
13*4882a593Smuzhiyun *
14*4882a593Smuzhiyun * ChangeLog:
15*4882a593Smuzhiyun * Sep.23, 2008 Harald Welte <laforge@gnumonks.org>
16*4882a593Smuzhiyun * -v0.95 rename driver from drivers/acpi/pcc_acpi.c to
17*4882a593Smuzhiyun * drivers/misc/panasonic-laptop.c
18*4882a593Smuzhiyun *
19*4882a593Smuzhiyun * Jul.04, 2008 Harald Welte <laforge@gnumonks.org>
20*4882a593Smuzhiyun * -v0.94 replace /proc interface with device attributes
21*4882a593Smuzhiyun * support {set,get}keycode on th input device
22*4882a593Smuzhiyun *
23*4882a593Smuzhiyun * Jun.27, 2008 Harald Welte <laforge@gnumonks.org>
24*4882a593Smuzhiyun * -v0.92 merge with 2.6.26-rc6 input API changes
25*4882a593Smuzhiyun * remove broken <= 2.6.15 kernel support
26*4882a593Smuzhiyun * resolve all compiler warnings
27*4882a593Smuzhiyun * various coding style fixes (checkpatch.pl)
28*4882a593Smuzhiyun * add support for backlight api
29*4882a593Smuzhiyun * major code restructuring
30*4882a593Smuzhiyun *
31*4882a593Smuzhiyun * Dac.28, 2007 Harald Welte <laforge@gnumonks.org>
32*4882a593Smuzhiyun * -v0.91 merge with 2.6.24-rc6 ACPI changes
33*4882a593Smuzhiyun *
34*4882a593Smuzhiyun * Nov.04, 2006 Hiroshi Miura <miura@da-cha.org>
35*4882a593Smuzhiyun * -v0.9 remove warning about section reference.
36*4882a593Smuzhiyun * remove acpi_os_free
37*4882a593Smuzhiyun * add /proc/acpi/pcc/brightness interface for HAL access
38*4882a593Smuzhiyun * merge dbronaugh's enhancement
39*4882a593Smuzhiyun * Aug.17, 2004 David Bronaugh (dbronaugh)
40*4882a593Smuzhiyun * - Added screen brightness setting interface
41*4882a593Smuzhiyun * Thanks to FreeBSD crew (acpi_panasonic.c)
42*4882a593Smuzhiyun * for the ideas I needed to accomplish it
43*4882a593Smuzhiyun *
44*4882a593Smuzhiyun * May.29, 2006 Hiroshi Miura <miura@da-cha.org>
45*4882a593Smuzhiyun * -v0.8.4 follow to change keyinput structure
46*4882a593Smuzhiyun * thanks Fabian Yamaguchi <fabs@cs.tu-berlin.de>,
47*4882a593Smuzhiyun * Jacob Bower <jacob.bower@ic.ac.uk> and
48*4882a593Smuzhiyun * Hiroshi Yokota for providing solutions.
49*4882a593Smuzhiyun *
50*4882a593Smuzhiyun * Oct.02, 2004 Hiroshi Miura <miura@da-cha.org>
51*4882a593Smuzhiyun * -v0.8.2 merge code of YOKOTA Hiroshi
52*4882a593Smuzhiyun * <yokota@netlab.is.tsukuba.ac.jp>.
53*4882a593Smuzhiyun * Add sticky key mode interface.
54*4882a593Smuzhiyun * Refactoring acpi_pcc_generate_keyinput().
55*4882a593Smuzhiyun *
56*4882a593Smuzhiyun * Sep.15, 2004 Hiroshi Miura <miura@da-cha.org>
57*4882a593Smuzhiyun * -v0.8 Generate key input event on input subsystem.
58*4882a593Smuzhiyun * This is based on yet another driver written by
59*4882a593Smuzhiyun * Ryuta Nakanishi.
60*4882a593Smuzhiyun *
61*4882a593Smuzhiyun * Sep.10, 2004 Hiroshi Miura <miura@da-cha.org>
62*4882a593Smuzhiyun * -v0.7 Change proc interface functions using seq_file
63*4882a593Smuzhiyun * facility as same as other ACPI drivers.
64*4882a593Smuzhiyun *
65*4882a593Smuzhiyun * Aug.28, 2004 Hiroshi Miura <miura@da-cha.org>
66*4882a593Smuzhiyun * -v0.6.4 Fix a silly error with status checking
67*4882a593Smuzhiyun *
68*4882a593Smuzhiyun * Aug.25, 2004 Hiroshi Miura <miura@da-cha.org>
69*4882a593Smuzhiyun * -v0.6.3 replace read_acpi_int by standard function
70*4882a593Smuzhiyun * acpi_evaluate_integer
71*4882a593Smuzhiyun * some clean up and make smart copyright notice.
72*4882a593Smuzhiyun * fix return value of pcc_acpi_get_key()
73*4882a593Smuzhiyun * fix checking return value of acpi_bus_register_driver()
74*4882a593Smuzhiyun *
75*4882a593Smuzhiyun * Aug.22, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
76*4882a593Smuzhiyun * -v0.6.2 Add check on ACPI data (num_sifr)
77*4882a593Smuzhiyun * Coding style cleanups, better error messages/handling
78*4882a593Smuzhiyun * Fixed an off-by-one error in memory allocation
79*4882a593Smuzhiyun *
80*4882a593Smuzhiyun * Aug.21, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
81*4882a593Smuzhiyun * -v0.6.1 Fix a silly error with status checking
82*4882a593Smuzhiyun *
83*4882a593Smuzhiyun * Aug.20, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
84*4882a593Smuzhiyun * - v0.6 Correct brightness controls to reflect reality
85*4882a593Smuzhiyun * based on information gleaned by Hiroshi Miura
86*4882a593Smuzhiyun * and discussions with Hiroshi Miura
87*4882a593Smuzhiyun *
88*4882a593Smuzhiyun * Aug.10, 2004 Hiroshi Miura <miura@da-cha.org>
89*4882a593Smuzhiyun * - v0.5 support LCD brightness control
90*4882a593Smuzhiyun * based on the disclosed information by MEI.
91*4882a593Smuzhiyun *
92*4882a593Smuzhiyun * Jul.25, 2004 Hiroshi Miura <miura@da-cha.org>
93*4882a593Smuzhiyun * - v0.4 first post version
94*4882a593Smuzhiyun * add function to retrive SIFR
95*4882a593Smuzhiyun *
96*4882a593Smuzhiyun * Jul.24, 2004 Hiroshi Miura <miura@da-cha.org>
97*4882a593Smuzhiyun * - v0.3 get proper status of hotkey
98*4882a593Smuzhiyun *
99*4882a593Smuzhiyun * Jul.22, 2004 Hiroshi Miura <miura@da-cha.org>
100*4882a593Smuzhiyun * - v0.2 add HotKey handler
101*4882a593Smuzhiyun *
102*4882a593Smuzhiyun * Jul.17, 2004 Hiroshi Miura <miura@da-cha.org>
103*4882a593Smuzhiyun * - v0.1 start from toshiba_acpi driver written by John Belmonte
104*4882a593Smuzhiyun */
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun #include <linux/kernel.h>
107*4882a593Smuzhiyun #include <linux/module.h>
108*4882a593Smuzhiyun #include <linux/init.h>
109*4882a593Smuzhiyun #include <linux/types.h>
110*4882a593Smuzhiyun #include <linux/backlight.h>
111*4882a593Smuzhiyun #include <linux/ctype.h>
112*4882a593Smuzhiyun #include <linux/seq_file.h>
113*4882a593Smuzhiyun #include <linux/uaccess.h>
114*4882a593Smuzhiyun #include <linux/slab.h>
115*4882a593Smuzhiyun #include <linux/acpi.h>
116*4882a593Smuzhiyun #include <linux/input.h>
117*4882a593Smuzhiyun #include <linux/input/sparse-keymap.h>
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun #ifndef ACPI_HOTKEY_COMPONENT
120*4882a593Smuzhiyun #define ACPI_HOTKEY_COMPONENT 0x10000000
121*4882a593Smuzhiyun #endif
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun #define _COMPONENT ACPI_HOTKEY_COMPONENT
124*4882a593Smuzhiyun
125*4882a593Smuzhiyun MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte");
126*4882a593Smuzhiyun MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops");
127*4882a593Smuzhiyun MODULE_LICENSE("GPL");
128*4882a593Smuzhiyun
129*4882a593Smuzhiyun #define LOGPREFIX "pcc_acpi: "
130*4882a593Smuzhiyun
131*4882a593Smuzhiyun /* Define ACPI PATHs */
132*4882a593Smuzhiyun /* Lets note hotkeys */
133*4882a593Smuzhiyun #define METHOD_HKEY_QUERY "HINF"
134*4882a593Smuzhiyun #define METHOD_HKEY_SQTY "SQTY"
135*4882a593Smuzhiyun #define METHOD_HKEY_SINF "SINF"
136*4882a593Smuzhiyun #define METHOD_HKEY_SSET "SSET"
137*4882a593Smuzhiyun #define HKEY_NOTIFY 0x80
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
140*4882a593Smuzhiyun #define ACPI_PCC_DEVICE_NAME "Hotkey"
141*4882a593Smuzhiyun #define ACPI_PCC_CLASS "pcc"
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun /* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
146*4882a593Smuzhiyun ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00
147*4882a593Smuzhiyun */
148*4882a593Smuzhiyun enum SINF_BITS { SINF_NUM_BATTERIES = 0,
149*4882a593Smuzhiyun SINF_LCD_TYPE,
150*4882a593Smuzhiyun SINF_AC_MAX_BRIGHT,
151*4882a593Smuzhiyun SINF_AC_MIN_BRIGHT,
152*4882a593Smuzhiyun SINF_AC_CUR_BRIGHT,
153*4882a593Smuzhiyun SINF_DC_MAX_BRIGHT,
154*4882a593Smuzhiyun SINF_DC_MIN_BRIGHT,
155*4882a593Smuzhiyun SINF_DC_CUR_BRIGHT,
156*4882a593Smuzhiyun SINF_MUTE,
157*4882a593Smuzhiyun SINF_RESERVED,
158*4882a593Smuzhiyun SINF_ENV_STATE,
159*4882a593Smuzhiyun SINF_STICKY_KEY = 0x80,
160*4882a593Smuzhiyun };
161*4882a593Smuzhiyun /* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun static int acpi_pcc_hotkey_add(struct acpi_device *device);
164*4882a593Smuzhiyun static int acpi_pcc_hotkey_remove(struct acpi_device *device);
165*4882a593Smuzhiyun static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event);
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun static const struct acpi_device_id pcc_device_ids[] = {
168*4882a593Smuzhiyun { "MAT0012", 0},
169*4882a593Smuzhiyun { "MAT0013", 0},
170*4882a593Smuzhiyun { "MAT0018", 0},
171*4882a593Smuzhiyun { "MAT0019", 0},
172*4882a593Smuzhiyun { "", 0},
173*4882a593Smuzhiyun };
174*4882a593Smuzhiyun MODULE_DEVICE_TABLE(acpi, pcc_device_ids);
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun #ifdef CONFIG_PM_SLEEP
177*4882a593Smuzhiyun static int acpi_pcc_hotkey_resume(struct device *dev);
178*4882a593Smuzhiyun #endif
179*4882a593Smuzhiyun static SIMPLE_DEV_PM_OPS(acpi_pcc_hotkey_pm, NULL, acpi_pcc_hotkey_resume);
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun static struct acpi_driver acpi_pcc_driver = {
182*4882a593Smuzhiyun .name = ACPI_PCC_DRIVER_NAME,
183*4882a593Smuzhiyun .class = ACPI_PCC_CLASS,
184*4882a593Smuzhiyun .ids = pcc_device_ids,
185*4882a593Smuzhiyun .ops = {
186*4882a593Smuzhiyun .add = acpi_pcc_hotkey_add,
187*4882a593Smuzhiyun .remove = acpi_pcc_hotkey_remove,
188*4882a593Smuzhiyun .notify = acpi_pcc_hotkey_notify,
189*4882a593Smuzhiyun },
190*4882a593Smuzhiyun .drv.pm = &acpi_pcc_hotkey_pm,
191*4882a593Smuzhiyun };
192*4882a593Smuzhiyun
193*4882a593Smuzhiyun static const struct key_entry panasonic_keymap[] = {
194*4882a593Smuzhiyun { KE_KEY, 0, { KEY_RESERVED } },
195*4882a593Smuzhiyun { KE_KEY, 1, { KEY_BRIGHTNESSDOWN } },
196*4882a593Smuzhiyun { KE_KEY, 2, { KEY_BRIGHTNESSUP } },
197*4882a593Smuzhiyun { KE_KEY, 3, { KEY_DISPLAYTOGGLE } },
198*4882a593Smuzhiyun { KE_KEY, 4, { KEY_MUTE } },
199*4882a593Smuzhiyun { KE_KEY, 5, { KEY_VOLUMEDOWN } },
200*4882a593Smuzhiyun { KE_KEY, 6, { KEY_VOLUMEUP } },
201*4882a593Smuzhiyun { KE_KEY, 7, { KEY_SLEEP } },
202*4882a593Smuzhiyun { KE_KEY, 8, { KEY_PROG1 } }, /* Change CPU boost */
203*4882a593Smuzhiyun { KE_KEY, 9, { KEY_BATTERY } },
204*4882a593Smuzhiyun { KE_KEY, 10, { KEY_SUSPEND } },
205*4882a593Smuzhiyun { KE_END, 0 }
206*4882a593Smuzhiyun };
207*4882a593Smuzhiyun
208*4882a593Smuzhiyun struct pcc_acpi {
209*4882a593Smuzhiyun acpi_handle handle;
210*4882a593Smuzhiyun unsigned long num_sifr;
211*4882a593Smuzhiyun int sticky_mode;
212*4882a593Smuzhiyun u32 *sinf;
213*4882a593Smuzhiyun struct acpi_device *device;
214*4882a593Smuzhiyun struct input_dev *input_dev;
215*4882a593Smuzhiyun struct backlight_device *backlight;
216*4882a593Smuzhiyun };
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun /* method access functions */
acpi_pcc_write_sset(struct pcc_acpi * pcc,int func,int val)219*4882a593Smuzhiyun static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val)
220*4882a593Smuzhiyun {
221*4882a593Smuzhiyun union acpi_object in_objs[] = {
222*4882a593Smuzhiyun { .integer.type = ACPI_TYPE_INTEGER,
223*4882a593Smuzhiyun .integer.value = func, },
224*4882a593Smuzhiyun { .integer.type = ACPI_TYPE_INTEGER,
225*4882a593Smuzhiyun .integer.value = val, },
226*4882a593Smuzhiyun };
227*4882a593Smuzhiyun struct acpi_object_list params = {
228*4882a593Smuzhiyun .count = ARRAY_SIZE(in_objs),
229*4882a593Smuzhiyun .pointer = in_objs,
230*4882a593Smuzhiyun };
231*4882a593Smuzhiyun acpi_status status = AE_OK;
232*4882a593Smuzhiyun
233*4882a593Smuzhiyun status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SSET,
234*4882a593Smuzhiyun ¶ms, NULL);
235*4882a593Smuzhiyun
236*4882a593Smuzhiyun return (status == AE_OK) ? 0 : -EIO;
237*4882a593Smuzhiyun }
238*4882a593Smuzhiyun
acpi_pcc_get_sqty(struct acpi_device * device)239*4882a593Smuzhiyun static inline int acpi_pcc_get_sqty(struct acpi_device *device)
240*4882a593Smuzhiyun {
241*4882a593Smuzhiyun unsigned long long s;
242*4882a593Smuzhiyun acpi_status status;
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun status = acpi_evaluate_integer(device->handle, METHOD_HKEY_SQTY,
245*4882a593Smuzhiyun NULL, &s);
246*4882a593Smuzhiyun if (ACPI_SUCCESS(status))
247*4882a593Smuzhiyun return s;
248*4882a593Smuzhiyun else {
249*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
250*4882a593Smuzhiyun "evaluation error HKEY.SQTY\n"));
251*4882a593Smuzhiyun return -EINVAL;
252*4882a593Smuzhiyun }
253*4882a593Smuzhiyun }
254*4882a593Smuzhiyun
acpi_pcc_retrieve_biosdata(struct pcc_acpi * pcc)255*4882a593Smuzhiyun static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc)
256*4882a593Smuzhiyun {
257*4882a593Smuzhiyun acpi_status status;
258*4882a593Smuzhiyun struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
259*4882a593Smuzhiyun union acpi_object *hkey = NULL;
260*4882a593Smuzhiyun int i;
261*4882a593Smuzhiyun
262*4882a593Smuzhiyun status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, NULL,
263*4882a593Smuzhiyun &buffer);
264*4882a593Smuzhiyun if (ACPI_FAILURE(status)) {
265*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
266*4882a593Smuzhiyun "evaluation error HKEY.SINF\n"));
267*4882a593Smuzhiyun return 0;
268*4882a593Smuzhiyun }
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun hkey = buffer.pointer;
271*4882a593Smuzhiyun if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) {
272*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n"));
273*4882a593Smuzhiyun status = AE_ERROR;
274*4882a593Smuzhiyun goto end;
275*4882a593Smuzhiyun }
276*4882a593Smuzhiyun
277*4882a593Smuzhiyun if (pcc->num_sifr < hkey->package.count) {
278*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
279*4882a593Smuzhiyun "SQTY reports bad SINF length\n"));
280*4882a593Smuzhiyun status = AE_ERROR;
281*4882a593Smuzhiyun goto end;
282*4882a593Smuzhiyun }
283*4882a593Smuzhiyun
284*4882a593Smuzhiyun for (i = 0; i < hkey->package.count; i++) {
285*4882a593Smuzhiyun union acpi_object *element = &(hkey->package.elements[i]);
286*4882a593Smuzhiyun if (likely(element->type == ACPI_TYPE_INTEGER)) {
287*4882a593Smuzhiyun pcc->sinf[i] = element->integer.value;
288*4882a593Smuzhiyun } else
289*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
290*4882a593Smuzhiyun "Invalid HKEY.SINF data\n"));
291*4882a593Smuzhiyun }
292*4882a593Smuzhiyun pcc->sinf[hkey->package.count] = -1;
293*4882a593Smuzhiyun
294*4882a593Smuzhiyun end:
295*4882a593Smuzhiyun kfree(buffer.pointer);
296*4882a593Smuzhiyun return status == AE_OK;
297*4882a593Smuzhiyun }
298*4882a593Smuzhiyun
299*4882a593Smuzhiyun /* backlight API interface functions */
300*4882a593Smuzhiyun
301*4882a593Smuzhiyun /* This driver currently treats AC and DC brightness identical,
302*4882a593Smuzhiyun * since we don't need to invent an interface to the core ACPI
303*4882a593Smuzhiyun * logic to receive events in case a power supply is plugged in
304*4882a593Smuzhiyun * or removed */
305*4882a593Smuzhiyun
bl_get(struct backlight_device * bd)306*4882a593Smuzhiyun static int bl_get(struct backlight_device *bd)
307*4882a593Smuzhiyun {
308*4882a593Smuzhiyun struct pcc_acpi *pcc = bl_get_data(bd);
309*4882a593Smuzhiyun
310*4882a593Smuzhiyun if (!acpi_pcc_retrieve_biosdata(pcc))
311*4882a593Smuzhiyun return -EIO;
312*4882a593Smuzhiyun
313*4882a593Smuzhiyun return pcc->sinf[SINF_AC_CUR_BRIGHT];
314*4882a593Smuzhiyun }
315*4882a593Smuzhiyun
bl_set_status(struct backlight_device * bd)316*4882a593Smuzhiyun static int bl_set_status(struct backlight_device *bd)
317*4882a593Smuzhiyun {
318*4882a593Smuzhiyun struct pcc_acpi *pcc = bl_get_data(bd);
319*4882a593Smuzhiyun int bright = bd->props.brightness;
320*4882a593Smuzhiyun int rc;
321*4882a593Smuzhiyun
322*4882a593Smuzhiyun if (!acpi_pcc_retrieve_biosdata(pcc))
323*4882a593Smuzhiyun return -EIO;
324*4882a593Smuzhiyun
325*4882a593Smuzhiyun if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT])
326*4882a593Smuzhiyun bright = pcc->sinf[SINF_AC_MIN_BRIGHT];
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun if (bright < pcc->sinf[SINF_DC_MIN_BRIGHT])
329*4882a593Smuzhiyun bright = pcc->sinf[SINF_DC_MIN_BRIGHT];
330*4882a593Smuzhiyun
331*4882a593Smuzhiyun if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT] ||
332*4882a593Smuzhiyun bright > pcc->sinf[SINF_AC_MAX_BRIGHT])
333*4882a593Smuzhiyun return -EINVAL;
334*4882a593Smuzhiyun
335*4882a593Smuzhiyun rc = acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, bright);
336*4882a593Smuzhiyun if (rc < 0)
337*4882a593Smuzhiyun return rc;
338*4882a593Smuzhiyun
339*4882a593Smuzhiyun return acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, bright);
340*4882a593Smuzhiyun }
341*4882a593Smuzhiyun
342*4882a593Smuzhiyun static const struct backlight_ops pcc_backlight_ops = {
343*4882a593Smuzhiyun .get_brightness = bl_get,
344*4882a593Smuzhiyun .update_status = bl_set_status,
345*4882a593Smuzhiyun };
346*4882a593Smuzhiyun
347*4882a593Smuzhiyun
348*4882a593Smuzhiyun /* sysfs user interface functions */
349*4882a593Smuzhiyun
show_numbatt(struct device * dev,struct device_attribute * attr,char * buf)350*4882a593Smuzhiyun static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
351*4882a593Smuzhiyun char *buf)
352*4882a593Smuzhiyun {
353*4882a593Smuzhiyun struct acpi_device *acpi = to_acpi_device(dev);
354*4882a593Smuzhiyun struct pcc_acpi *pcc = acpi_driver_data(acpi);
355*4882a593Smuzhiyun
356*4882a593Smuzhiyun if (!acpi_pcc_retrieve_biosdata(pcc))
357*4882a593Smuzhiyun return -EIO;
358*4882a593Smuzhiyun
359*4882a593Smuzhiyun return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]);
360*4882a593Smuzhiyun }
361*4882a593Smuzhiyun
show_lcdtype(struct device * dev,struct device_attribute * attr,char * buf)362*4882a593Smuzhiyun static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr,
363*4882a593Smuzhiyun char *buf)
364*4882a593Smuzhiyun {
365*4882a593Smuzhiyun struct acpi_device *acpi = to_acpi_device(dev);
366*4882a593Smuzhiyun struct pcc_acpi *pcc = acpi_driver_data(acpi);
367*4882a593Smuzhiyun
368*4882a593Smuzhiyun if (!acpi_pcc_retrieve_biosdata(pcc))
369*4882a593Smuzhiyun return -EIO;
370*4882a593Smuzhiyun
371*4882a593Smuzhiyun return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_LCD_TYPE]);
372*4882a593Smuzhiyun }
373*4882a593Smuzhiyun
show_mute(struct device * dev,struct device_attribute * attr,char * buf)374*4882a593Smuzhiyun static ssize_t show_mute(struct device *dev, struct device_attribute *attr,
375*4882a593Smuzhiyun char *buf)
376*4882a593Smuzhiyun {
377*4882a593Smuzhiyun struct acpi_device *acpi = to_acpi_device(dev);
378*4882a593Smuzhiyun struct pcc_acpi *pcc = acpi_driver_data(acpi);
379*4882a593Smuzhiyun
380*4882a593Smuzhiyun if (!acpi_pcc_retrieve_biosdata(pcc))
381*4882a593Smuzhiyun return -EIO;
382*4882a593Smuzhiyun
383*4882a593Smuzhiyun return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_MUTE]);
384*4882a593Smuzhiyun }
385*4882a593Smuzhiyun
show_sticky(struct device * dev,struct device_attribute * attr,char * buf)386*4882a593Smuzhiyun static ssize_t show_sticky(struct device *dev, struct device_attribute *attr,
387*4882a593Smuzhiyun char *buf)
388*4882a593Smuzhiyun {
389*4882a593Smuzhiyun struct acpi_device *acpi = to_acpi_device(dev);
390*4882a593Smuzhiyun struct pcc_acpi *pcc = acpi_driver_data(acpi);
391*4882a593Smuzhiyun
392*4882a593Smuzhiyun if (!acpi_pcc_retrieve_biosdata(pcc))
393*4882a593Smuzhiyun return -EIO;
394*4882a593Smuzhiyun
395*4882a593Smuzhiyun return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_STICKY_KEY]);
396*4882a593Smuzhiyun }
397*4882a593Smuzhiyun
set_sticky(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)398*4882a593Smuzhiyun static ssize_t set_sticky(struct device *dev, struct device_attribute *attr,
399*4882a593Smuzhiyun const char *buf, size_t count)
400*4882a593Smuzhiyun {
401*4882a593Smuzhiyun struct acpi_device *acpi = to_acpi_device(dev);
402*4882a593Smuzhiyun struct pcc_acpi *pcc = acpi_driver_data(acpi);
403*4882a593Smuzhiyun int val;
404*4882a593Smuzhiyun
405*4882a593Smuzhiyun if (count && sscanf(buf, "%i", &val) == 1 &&
406*4882a593Smuzhiyun (val == 0 || val == 1)) {
407*4882a593Smuzhiyun acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, val);
408*4882a593Smuzhiyun pcc->sticky_mode = val;
409*4882a593Smuzhiyun }
410*4882a593Smuzhiyun
411*4882a593Smuzhiyun return count;
412*4882a593Smuzhiyun }
413*4882a593Smuzhiyun
414*4882a593Smuzhiyun static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL);
415*4882a593Smuzhiyun static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL);
416*4882a593Smuzhiyun static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL);
417*4882a593Smuzhiyun static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky);
418*4882a593Smuzhiyun
419*4882a593Smuzhiyun static struct attribute *pcc_sysfs_entries[] = {
420*4882a593Smuzhiyun &dev_attr_numbatt.attr,
421*4882a593Smuzhiyun &dev_attr_lcdtype.attr,
422*4882a593Smuzhiyun &dev_attr_mute.attr,
423*4882a593Smuzhiyun &dev_attr_sticky_key.attr,
424*4882a593Smuzhiyun NULL,
425*4882a593Smuzhiyun };
426*4882a593Smuzhiyun
427*4882a593Smuzhiyun static const struct attribute_group pcc_attr_group = {
428*4882a593Smuzhiyun .name = NULL, /* put in device directory */
429*4882a593Smuzhiyun .attrs = pcc_sysfs_entries,
430*4882a593Smuzhiyun };
431*4882a593Smuzhiyun
432*4882a593Smuzhiyun
433*4882a593Smuzhiyun /* hotkey input device driver */
434*4882a593Smuzhiyun
435*4882a593Smuzhiyun static int sleep_keydown_seen;
acpi_pcc_generate_keyinput(struct pcc_acpi * pcc)436*4882a593Smuzhiyun static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
437*4882a593Smuzhiyun {
438*4882a593Smuzhiyun struct input_dev *hotk_input_dev = pcc->input_dev;
439*4882a593Smuzhiyun int rc;
440*4882a593Smuzhiyun unsigned long long result;
441*4882a593Smuzhiyun
442*4882a593Smuzhiyun rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY,
443*4882a593Smuzhiyun NULL, &result);
444*4882a593Smuzhiyun if (ACPI_FAILURE(rc)) {
445*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
446*4882a593Smuzhiyun "error getting hotkey status\n"));
447*4882a593Smuzhiyun return;
448*4882a593Smuzhiyun }
449*4882a593Smuzhiyun
450*4882a593Smuzhiyun /* hack: some firmware sends no key down for sleep / hibernate */
451*4882a593Smuzhiyun if ((result & 0xf) == 0x7 || (result & 0xf) == 0xa) {
452*4882a593Smuzhiyun if (result & 0x80)
453*4882a593Smuzhiyun sleep_keydown_seen = 1;
454*4882a593Smuzhiyun if (!sleep_keydown_seen)
455*4882a593Smuzhiyun sparse_keymap_report_event(hotk_input_dev,
456*4882a593Smuzhiyun result & 0xf, 0x80, false);
457*4882a593Smuzhiyun }
458*4882a593Smuzhiyun
459*4882a593Smuzhiyun if (!sparse_keymap_report_event(hotk_input_dev,
460*4882a593Smuzhiyun result & 0xf, result & 0x80, false))
461*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
462*4882a593Smuzhiyun "Unknown hotkey event: %d\n", result));
463*4882a593Smuzhiyun }
464*4882a593Smuzhiyun
acpi_pcc_hotkey_notify(struct acpi_device * device,u32 event)465*4882a593Smuzhiyun static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
466*4882a593Smuzhiyun {
467*4882a593Smuzhiyun struct pcc_acpi *pcc = acpi_driver_data(device);
468*4882a593Smuzhiyun
469*4882a593Smuzhiyun switch (event) {
470*4882a593Smuzhiyun case HKEY_NOTIFY:
471*4882a593Smuzhiyun acpi_pcc_generate_keyinput(pcc);
472*4882a593Smuzhiyun break;
473*4882a593Smuzhiyun default:
474*4882a593Smuzhiyun /* nothing to do */
475*4882a593Smuzhiyun break;
476*4882a593Smuzhiyun }
477*4882a593Smuzhiyun }
478*4882a593Smuzhiyun
acpi_pcc_init_input(struct pcc_acpi * pcc)479*4882a593Smuzhiyun static int acpi_pcc_init_input(struct pcc_acpi *pcc)
480*4882a593Smuzhiyun {
481*4882a593Smuzhiyun struct input_dev *input_dev;
482*4882a593Smuzhiyun int error;
483*4882a593Smuzhiyun
484*4882a593Smuzhiyun input_dev = input_allocate_device();
485*4882a593Smuzhiyun if (!input_dev)
486*4882a593Smuzhiyun return -ENOMEM;
487*4882a593Smuzhiyun
488*4882a593Smuzhiyun input_dev->name = ACPI_PCC_DRIVER_NAME;
489*4882a593Smuzhiyun input_dev->phys = ACPI_PCC_INPUT_PHYS;
490*4882a593Smuzhiyun input_dev->id.bustype = BUS_HOST;
491*4882a593Smuzhiyun input_dev->id.vendor = 0x0001;
492*4882a593Smuzhiyun input_dev->id.product = 0x0001;
493*4882a593Smuzhiyun input_dev->id.version = 0x0100;
494*4882a593Smuzhiyun
495*4882a593Smuzhiyun error = sparse_keymap_setup(input_dev, panasonic_keymap, NULL);
496*4882a593Smuzhiyun if (error) {
497*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
498*4882a593Smuzhiyun "Unable to setup input device keymap\n"));
499*4882a593Smuzhiyun goto err_free_dev;
500*4882a593Smuzhiyun }
501*4882a593Smuzhiyun
502*4882a593Smuzhiyun error = input_register_device(input_dev);
503*4882a593Smuzhiyun if (error) {
504*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
505*4882a593Smuzhiyun "Unable to register input device\n"));
506*4882a593Smuzhiyun goto err_free_dev;
507*4882a593Smuzhiyun }
508*4882a593Smuzhiyun
509*4882a593Smuzhiyun pcc->input_dev = input_dev;
510*4882a593Smuzhiyun return 0;
511*4882a593Smuzhiyun
512*4882a593Smuzhiyun err_free_dev:
513*4882a593Smuzhiyun input_free_device(input_dev);
514*4882a593Smuzhiyun return error;
515*4882a593Smuzhiyun }
516*4882a593Smuzhiyun
517*4882a593Smuzhiyun /* kernel module interface */
518*4882a593Smuzhiyun
519*4882a593Smuzhiyun #ifdef CONFIG_PM_SLEEP
acpi_pcc_hotkey_resume(struct device * dev)520*4882a593Smuzhiyun static int acpi_pcc_hotkey_resume(struct device *dev)
521*4882a593Smuzhiyun {
522*4882a593Smuzhiyun struct pcc_acpi *pcc;
523*4882a593Smuzhiyun
524*4882a593Smuzhiyun if (!dev)
525*4882a593Smuzhiyun return -EINVAL;
526*4882a593Smuzhiyun
527*4882a593Smuzhiyun pcc = acpi_driver_data(to_acpi_device(dev));
528*4882a593Smuzhiyun if (!pcc)
529*4882a593Smuzhiyun return -EINVAL;
530*4882a593Smuzhiyun
531*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n",
532*4882a593Smuzhiyun pcc->sticky_mode));
533*4882a593Smuzhiyun
534*4882a593Smuzhiyun return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode);
535*4882a593Smuzhiyun }
536*4882a593Smuzhiyun #endif
537*4882a593Smuzhiyun
acpi_pcc_hotkey_add(struct acpi_device * device)538*4882a593Smuzhiyun static int acpi_pcc_hotkey_add(struct acpi_device *device)
539*4882a593Smuzhiyun {
540*4882a593Smuzhiyun struct backlight_properties props;
541*4882a593Smuzhiyun struct pcc_acpi *pcc;
542*4882a593Smuzhiyun int num_sifr, result;
543*4882a593Smuzhiyun
544*4882a593Smuzhiyun if (!device)
545*4882a593Smuzhiyun return -EINVAL;
546*4882a593Smuzhiyun
547*4882a593Smuzhiyun num_sifr = acpi_pcc_get_sqty(device);
548*4882a593Smuzhiyun
549*4882a593Smuzhiyun if (num_sifr < 0 || num_sifr > 255) {
550*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr out of range"));
551*4882a593Smuzhiyun return -ENODEV;
552*4882a593Smuzhiyun }
553*4882a593Smuzhiyun
554*4882a593Smuzhiyun pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL);
555*4882a593Smuzhiyun if (!pcc) {
556*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
557*4882a593Smuzhiyun "Couldn't allocate mem for pcc"));
558*4882a593Smuzhiyun return -ENOMEM;
559*4882a593Smuzhiyun }
560*4882a593Smuzhiyun
561*4882a593Smuzhiyun pcc->sinf = kcalloc(num_sifr + 1, sizeof(u32), GFP_KERNEL);
562*4882a593Smuzhiyun if (!pcc->sinf) {
563*4882a593Smuzhiyun result = -ENOMEM;
564*4882a593Smuzhiyun goto out_hotkey;
565*4882a593Smuzhiyun }
566*4882a593Smuzhiyun
567*4882a593Smuzhiyun pcc->device = device;
568*4882a593Smuzhiyun pcc->handle = device->handle;
569*4882a593Smuzhiyun pcc->num_sifr = num_sifr;
570*4882a593Smuzhiyun device->driver_data = pcc;
571*4882a593Smuzhiyun strcpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
572*4882a593Smuzhiyun strcpy(acpi_device_class(device), ACPI_PCC_CLASS);
573*4882a593Smuzhiyun
574*4882a593Smuzhiyun result = acpi_pcc_init_input(pcc);
575*4882a593Smuzhiyun if (result) {
576*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
577*4882a593Smuzhiyun "Error installing keyinput handler\n"));
578*4882a593Smuzhiyun goto out_sinf;
579*4882a593Smuzhiyun }
580*4882a593Smuzhiyun
581*4882a593Smuzhiyun if (!acpi_pcc_retrieve_biosdata(pcc)) {
582*4882a593Smuzhiyun ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
583*4882a593Smuzhiyun "Couldn't retrieve BIOS data\n"));
584*4882a593Smuzhiyun result = -EIO;
585*4882a593Smuzhiyun goto out_input;
586*4882a593Smuzhiyun }
587*4882a593Smuzhiyun /* initialize backlight */
588*4882a593Smuzhiyun memset(&props, 0, sizeof(struct backlight_properties));
589*4882a593Smuzhiyun props.type = BACKLIGHT_PLATFORM;
590*4882a593Smuzhiyun props.max_brightness = pcc->sinf[SINF_AC_MAX_BRIGHT];
591*4882a593Smuzhiyun pcc->backlight = backlight_device_register("panasonic", NULL, pcc,
592*4882a593Smuzhiyun &pcc_backlight_ops, &props);
593*4882a593Smuzhiyun if (IS_ERR(pcc->backlight)) {
594*4882a593Smuzhiyun result = PTR_ERR(pcc->backlight);
595*4882a593Smuzhiyun goto out_input;
596*4882a593Smuzhiyun }
597*4882a593Smuzhiyun
598*4882a593Smuzhiyun /* read the initial brightness setting from the hardware */
599*4882a593Smuzhiyun pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
600*4882a593Smuzhiyun
601*4882a593Smuzhiyun /* read the initial sticky key mode from the hardware */
602*4882a593Smuzhiyun pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY];
603*4882a593Smuzhiyun
604*4882a593Smuzhiyun /* add sysfs attributes */
605*4882a593Smuzhiyun result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group);
606*4882a593Smuzhiyun if (result)
607*4882a593Smuzhiyun goto out_backlight;
608*4882a593Smuzhiyun
609*4882a593Smuzhiyun return 0;
610*4882a593Smuzhiyun
611*4882a593Smuzhiyun out_backlight:
612*4882a593Smuzhiyun backlight_device_unregister(pcc->backlight);
613*4882a593Smuzhiyun out_input:
614*4882a593Smuzhiyun input_unregister_device(pcc->input_dev);
615*4882a593Smuzhiyun out_sinf:
616*4882a593Smuzhiyun kfree(pcc->sinf);
617*4882a593Smuzhiyun out_hotkey:
618*4882a593Smuzhiyun kfree(pcc);
619*4882a593Smuzhiyun
620*4882a593Smuzhiyun return result;
621*4882a593Smuzhiyun }
622*4882a593Smuzhiyun
acpi_pcc_hotkey_remove(struct acpi_device * device)623*4882a593Smuzhiyun static int acpi_pcc_hotkey_remove(struct acpi_device *device)
624*4882a593Smuzhiyun {
625*4882a593Smuzhiyun struct pcc_acpi *pcc = acpi_driver_data(device);
626*4882a593Smuzhiyun
627*4882a593Smuzhiyun if (!device || !pcc)
628*4882a593Smuzhiyun return -EINVAL;
629*4882a593Smuzhiyun
630*4882a593Smuzhiyun sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
631*4882a593Smuzhiyun
632*4882a593Smuzhiyun backlight_device_unregister(pcc->backlight);
633*4882a593Smuzhiyun
634*4882a593Smuzhiyun input_unregister_device(pcc->input_dev);
635*4882a593Smuzhiyun
636*4882a593Smuzhiyun kfree(pcc->sinf);
637*4882a593Smuzhiyun kfree(pcc);
638*4882a593Smuzhiyun
639*4882a593Smuzhiyun return 0;
640*4882a593Smuzhiyun }
641*4882a593Smuzhiyun
642*4882a593Smuzhiyun module_acpi_driver(acpi_pcc_driver);
643