xref: /OK3568_Linux_fs/kernel/drivers/platform/x86/intel_oaktrail.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * Intel OakTrail Platform support
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * Copyright (C) 2010-2011 Intel Corporation
6*4882a593Smuzhiyun  * Author: Yin Kangkai (kangkai.yin@intel.com)
7*4882a593Smuzhiyun  *
8*4882a593Smuzhiyun  * based on Compal driver, Copyright (C) 2008 Cezary Jackiewicz
9*4882a593Smuzhiyun  * <cezary.jackiewicz (at) gmail.com>, based on MSI driver
10*4882a593Smuzhiyun  * Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
11*4882a593Smuzhiyun  *
12*4882a593Smuzhiyun  * This driver does below things:
13*4882a593Smuzhiyun  * 1. registers itself in the Linux backlight control in
14*4882a593Smuzhiyun  *    /sys/class/backlight/intel_oaktrail/
15*4882a593Smuzhiyun  *
16*4882a593Smuzhiyun  * 2. registers in the rfkill subsystem here: /sys/class/rfkill/rfkillX/
17*4882a593Smuzhiyun  *    for these components: wifi, bluetooth, wwan (3g), gps
18*4882a593Smuzhiyun  *
19*4882a593Smuzhiyun  * This driver might work on other products based on Oaktrail. If you
20*4882a593Smuzhiyun  * want to try it you can pass force=1 as argument to the module which
21*4882a593Smuzhiyun  * will force it to load even when the DMI data doesn't identify the
22*4882a593Smuzhiyun  * product as compatible.
23*4882a593Smuzhiyun  */
24*4882a593Smuzhiyun 
25*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
26*4882a593Smuzhiyun 
27*4882a593Smuzhiyun #include <linux/acpi.h>
28*4882a593Smuzhiyun #include <linux/backlight.h>
29*4882a593Smuzhiyun #include <linux/dmi.h>
30*4882a593Smuzhiyun #include <linux/err.h>
31*4882a593Smuzhiyun #include <linux/fb.h>
32*4882a593Smuzhiyun #include <linux/i2c.h>
33*4882a593Smuzhiyun #include <linux/kernel.h>
34*4882a593Smuzhiyun #include <linux/module.h>
35*4882a593Smuzhiyun #include <linux/mutex.h>
36*4882a593Smuzhiyun #include <linux/platform_device.h>
37*4882a593Smuzhiyun #include <linux/rfkill.h>
38*4882a593Smuzhiyun 
39*4882a593Smuzhiyun #include <acpi/video.h>
40*4882a593Smuzhiyun 
41*4882a593Smuzhiyun #define DRIVER_NAME	"intel_oaktrail"
42*4882a593Smuzhiyun #define DRIVER_VERSION	"0.4ac1"
43*4882a593Smuzhiyun 
44*4882a593Smuzhiyun /*
45*4882a593Smuzhiyun  * This is the devices status address in EC space, and the control bits
46*4882a593Smuzhiyun  * definition:
47*4882a593Smuzhiyun  *
48*4882a593Smuzhiyun  * (1 << 0):	Camera enable/disable, RW.
49*4882a593Smuzhiyun  * (1 << 1):	Bluetooth enable/disable, RW.
50*4882a593Smuzhiyun  * (1 << 2):	GPS enable/disable, RW.
51*4882a593Smuzhiyun  * (1 << 3):	WiFi enable/disable, RW.
52*4882a593Smuzhiyun  * (1 << 4):	WWAN (3G) enable/disable, RW.
53*4882a593Smuzhiyun  * (1 << 5):	Touchscreen enable/disable, Read Only.
54*4882a593Smuzhiyun  */
55*4882a593Smuzhiyun #define OT_EC_DEVICE_STATE_ADDRESS	0xD6
56*4882a593Smuzhiyun 
57*4882a593Smuzhiyun #define OT_EC_CAMERA_MASK	(1 << 0)
58*4882a593Smuzhiyun #define OT_EC_BT_MASK		(1 << 1)
59*4882a593Smuzhiyun #define OT_EC_GPS_MASK		(1 << 2)
60*4882a593Smuzhiyun #define OT_EC_WIFI_MASK		(1 << 3)
61*4882a593Smuzhiyun #define OT_EC_WWAN_MASK		(1 << 4)
62*4882a593Smuzhiyun #define OT_EC_TS_MASK		(1 << 5)
63*4882a593Smuzhiyun 
64*4882a593Smuzhiyun /*
65*4882a593Smuzhiyun  * This is the address in EC space and commands used to control LCD backlight:
66*4882a593Smuzhiyun  *
67*4882a593Smuzhiyun  * Two steps needed to change the LCD backlight:
68*4882a593Smuzhiyun  *   1. write the backlight percentage into OT_EC_BL_BRIGHTNESS_ADDRESS;
69*4882a593Smuzhiyun  *   2. write OT_EC_BL_CONTROL_ON_DATA into OT_EC_BL_CONTROL_ADDRESS.
70*4882a593Smuzhiyun  *
71*4882a593Smuzhiyun  * To read the LCD back light, just read out the value from
72*4882a593Smuzhiyun  * OT_EC_BL_BRIGHTNESS_ADDRESS.
73*4882a593Smuzhiyun  *
74*4882a593Smuzhiyun  * LCD backlight brightness range: 0 - 100 (OT_EC_BL_BRIGHTNESS_MAX)
75*4882a593Smuzhiyun  */
76*4882a593Smuzhiyun #define OT_EC_BL_BRIGHTNESS_ADDRESS	0x44
77*4882a593Smuzhiyun #define OT_EC_BL_BRIGHTNESS_MAX		100
78*4882a593Smuzhiyun #define OT_EC_BL_CONTROL_ADDRESS	0x3A
79*4882a593Smuzhiyun #define OT_EC_BL_CONTROL_ON_DATA	0x1A
80*4882a593Smuzhiyun 
81*4882a593Smuzhiyun 
82*4882a593Smuzhiyun static bool force;
83*4882a593Smuzhiyun module_param(force, bool, 0);
84*4882a593Smuzhiyun MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
85*4882a593Smuzhiyun 
86*4882a593Smuzhiyun static struct platform_device *oaktrail_device;
87*4882a593Smuzhiyun static struct backlight_device *oaktrail_bl_device;
88*4882a593Smuzhiyun static struct rfkill *bt_rfkill;
89*4882a593Smuzhiyun static struct rfkill *gps_rfkill;
90*4882a593Smuzhiyun static struct rfkill *wifi_rfkill;
91*4882a593Smuzhiyun static struct rfkill *wwan_rfkill;
92*4882a593Smuzhiyun 
93*4882a593Smuzhiyun 
94*4882a593Smuzhiyun /* rfkill */
oaktrail_rfkill_set(void * data,bool blocked)95*4882a593Smuzhiyun static int oaktrail_rfkill_set(void *data, bool blocked)
96*4882a593Smuzhiyun {
97*4882a593Smuzhiyun 	u8 value;
98*4882a593Smuzhiyun 	u8 result;
99*4882a593Smuzhiyun 	unsigned long radio = (unsigned long) data;
100*4882a593Smuzhiyun 
101*4882a593Smuzhiyun 	ec_read(OT_EC_DEVICE_STATE_ADDRESS, &result);
102*4882a593Smuzhiyun 
103*4882a593Smuzhiyun 	if (!blocked)
104*4882a593Smuzhiyun 		value = (u8) (result | radio);
105*4882a593Smuzhiyun 	else
106*4882a593Smuzhiyun 		value = (u8) (result & ~radio);
107*4882a593Smuzhiyun 
108*4882a593Smuzhiyun 	ec_write(OT_EC_DEVICE_STATE_ADDRESS, value);
109*4882a593Smuzhiyun 
110*4882a593Smuzhiyun 	return 0;
111*4882a593Smuzhiyun }
112*4882a593Smuzhiyun 
113*4882a593Smuzhiyun static const struct rfkill_ops oaktrail_rfkill_ops = {
114*4882a593Smuzhiyun 	.set_block = oaktrail_rfkill_set,
115*4882a593Smuzhiyun };
116*4882a593Smuzhiyun 
oaktrail_rfkill_new(char * name,enum rfkill_type type,unsigned long mask)117*4882a593Smuzhiyun static struct rfkill *oaktrail_rfkill_new(char *name, enum rfkill_type type,
118*4882a593Smuzhiyun 					  unsigned long mask)
119*4882a593Smuzhiyun {
120*4882a593Smuzhiyun 	struct rfkill *rfkill_dev;
121*4882a593Smuzhiyun 	u8 value;
122*4882a593Smuzhiyun 	int err;
123*4882a593Smuzhiyun 
124*4882a593Smuzhiyun 	rfkill_dev = rfkill_alloc(name, &oaktrail_device->dev, type,
125*4882a593Smuzhiyun 				  &oaktrail_rfkill_ops, (void *)mask);
126*4882a593Smuzhiyun 	if (!rfkill_dev)
127*4882a593Smuzhiyun 		return ERR_PTR(-ENOMEM);
128*4882a593Smuzhiyun 
129*4882a593Smuzhiyun 	ec_read(OT_EC_DEVICE_STATE_ADDRESS, &value);
130*4882a593Smuzhiyun 	rfkill_init_sw_state(rfkill_dev, (value & mask) != 1);
131*4882a593Smuzhiyun 
132*4882a593Smuzhiyun 	err = rfkill_register(rfkill_dev);
133*4882a593Smuzhiyun 	if (err) {
134*4882a593Smuzhiyun 		rfkill_destroy(rfkill_dev);
135*4882a593Smuzhiyun 		return ERR_PTR(err);
136*4882a593Smuzhiyun 	}
137*4882a593Smuzhiyun 
138*4882a593Smuzhiyun 	return rfkill_dev;
139*4882a593Smuzhiyun }
140*4882a593Smuzhiyun 
__oaktrail_rfkill_cleanup(struct rfkill * rf)141*4882a593Smuzhiyun static inline void __oaktrail_rfkill_cleanup(struct rfkill *rf)
142*4882a593Smuzhiyun {
143*4882a593Smuzhiyun 	if (rf) {
144*4882a593Smuzhiyun 		rfkill_unregister(rf);
145*4882a593Smuzhiyun 		rfkill_destroy(rf);
146*4882a593Smuzhiyun 	}
147*4882a593Smuzhiyun }
148*4882a593Smuzhiyun 
oaktrail_rfkill_cleanup(void)149*4882a593Smuzhiyun static void oaktrail_rfkill_cleanup(void)
150*4882a593Smuzhiyun {
151*4882a593Smuzhiyun 	__oaktrail_rfkill_cleanup(wifi_rfkill);
152*4882a593Smuzhiyun 	__oaktrail_rfkill_cleanup(bt_rfkill);
153*4882a593Smuzhiyun 	__oaktrail_rfkill_cleanup(gps_rfkill);
154*4882a593Smuzhiyun 	__oaktrail_rfkill_cleanup(wwan_rfkill);
155*4882a593Smuzhiyun }
156*4882a593Smuzhiyun 
oaktrail_rfkill_init(void)157*4882a593Smuzhiyun static int oaktrail_rfkill_init(void)
158*4882a593Smuzhiyun {
159*4882a593Smuzhiyun 	int ret;
160*4882a593Smuzhiyun 
161*4882a593Smuzhiyun 	wifi_rfkill = oaktrail_rfkill_new("oaktrail-wifi",
162*4882a593Smuzhiyun 					  RFKILL_TYPE_WLAN,
163*4882a593Smuzhiyun 					  OT_EC_WIFI_MASK);
164*4882a593Smuzhiyun 	if (IS_ERR(wifi_rfkill)) {
165*4882a593Smuzhiyun 		ret = PTR_ERR(wifi_rfkill);
166*4882a593Smuzhiyun 		wifi_rfkill = NULL;
167*4882a593Smuzhiyun 		goto cleanup;
168*4882a593Smuzhiyun 	}
169*4882a593Smuzhiyun 
170*4882a593Smuzhiyun 	bt_rfkill = oaktrail_rfkill_new("oaktrail-bluetooth",
171*4882a593Smuzhiyun 					RFKILL_TYPE_BLUETOOTH,
172*4882a593Smuzhiyun 					OT_EC_BT_MASK);
173*4882a593Smuzhiyun 	if (IS_ERR(bt_rfkill)) {
174*4882a593Smuzhiyun 		ret = PTR_ERR(bt_rfkill);
175*4882a593Smuzhiyun 		bt_rfkill = NULL;
176*4882a593Smuzhiyun 		goto cleanup;
177*4882a593Smuzhiyun 	}
178*4882a593Smuzhiyun 
179*4882a593Smuzhiyun 	gps_rfkill = oaktrail_rfkill_new("oaktrail-gps",
180*4882a593Smuzhiyun 					 RFKILL_TYPE_GPS,
181*4882a593Smuzhiyun 					 OT_EC_GPS_MASK);
182*4882a593Smuzhiyun 	if (IS_ERR(gps_rfkill)) {
183*4882a593Smuzhiyun 		ret = PTR_ERR(gps_rfkill);
184*4882a593Smuzhiyun 		gps_rfkill = NULL;
185*4882a593Smuzhiyun 		goto cleanup;
186*4882a593Smuzhiyun 	}
187*4882a593Smuzhiyun 
188*4882a593Smuzhiyun 	wwan_rfkill = oaktrail_rfkill_new("oaktrail-wwan",
189*4882a593Smuzhiyun 					  RFKILL_TYPE_WWAN,
190*4882a593Smuzhiyun 					  OT_EC_WWAN_MASK);
191*4882a593Smuzhiyun 	if (IS_ERR(wwan_rfkill)) {
192*4882a593Smuzhiyun 		ret = PTR_ERR(wwan_rfkill);
193*4882a593Smuzhiyun 		wwan_rfkill = NULL;
194*4882a593Smuzhiyun 		goto cleanup;
195*4882a593Smuzhiyun 	}
196*4882a593Smuzhiyun 
197*4882a593Smuzhiyun 	return 0;
198*4882a593Smuzhiyun 
199*4882a593Smuzhiyun cleanup:
200*4882a593Smuzhiyun 	oaktrail_rfkill_cleanup();
201*4882a593Smuzhiyun 	return ret;
202*4882a593Smuzhiyun }
203*4882a593Smuzhiyun 
204*4882a593Smuzhiyun 
205*4882a593Smuzhiyun /* backlight */
get_backlight_brightness(struct backlight_device * b)206*4882a593Smuzhiyun static int get_backlight_brightness(struct backlight_device *b)
207*4882a593Smuzhiyun {
208*4882a593Smuzhiyun 	u8 value;
209*4882a593Smuzhiyun 	ec_read(OT_EC_BL_BRIGHTNESS_ADDRESS, &value);
210*4882a593Smuzhiyun 
211*4882a593Smuzhiyun 	return value;
212*4882a593Smuzhiyun }
213*4882a593Smuzhiyun 
set_backlight_brightness(struct backlight_device * b)214*4882a593Smuzhiyun static int set_backlight_brightness(struct backlight_device *b)
215*4882a593Smuzhiyun {
216*4882a593Smuzhiyun 	u8 percent = (u8) b->props.brightness;
217*4882a593Smuzhiyun 	if (percent < 0 || percent > OT_EC_BL_BRIGHTNESS_MAX)
218*4882a593Smuzhiyun 		return -EINVAL;
219*4882a593Smuzhiyun 
220*4882a593Smuzhiyun 	ec_write(OT_EC_BL_BRIGHTNESS_ADDRESS, percent);
221*4882a593Smuzhiyun 	ec_write(OT_EC_BL_CONTROL_ADDRESS, OT_EC_BL_CONTROL_ON_DATA);
222*4882a593Smuzhiyun 
223*4882a593Smuzhiyun 	return 0;
224*4882a593Smuzhiyun }
225*4882a593Smuzhiyun 
226*4882a593Smuzhiyun static const struct backlight_ops oaktrail_bl_ops = {
227*4882a593Smuzhiyun 	.get_brightness = get_backlight_brightness,
228*4882a593Smuzhiyun 	.update_status	= set_backlight_brightness,
229*4882a593Smuzhiyun };
230*4882a593Smuzhiyun 
oaktrail_backlight_init(void)231*4882a593Smuzhiyun static int oaktrail_backlight_init(void)
232*4882a593Smuzhiyun {
233*4882a593Smuzhiyun 	struct backlight_device *bd;
234*4882a593Smuzhiyun 	struct backlight_properties props;
235*4882a593Smuzhiyun 
236*4882a593Smuzhiyun 	memset(&props, 0, sizeof(struct backlight_properties));
237*4882a593Smuzhiyun 	props.type = BACKLIGHT_PLATFORM;
238*4882a593Smuzhiyun 	props.max_brightness = OT_EC_BL_BRIGHTNESS_MAX;
239*4882a593Smuzhiyun 	bd = backlight_device_register(DRIVER_NAME,
240*4882a593Smuzhiyun 				       &oaktrail_device->dev, NULL,
241*4882a593Smuzhiyun 				       &oaktrail_bl_ops,
242*4882a593Smuzhiyun 				       &props);
243*4882a593Smuzhiyun 
244*4882a593Smuzhiyun 	if (IS_ERR(bd)) {
245*4882a593Smuzhiyun 		oaktrail_bl_device = NULL;
246*4882a593Smuzhiyun 		pr_warn("Unable to register backlight device\n");
247*4882a593Smuzhiyun 		return PTR_ERR(bd);
248*4882a593Smuzhiyun 	}
249*4882a593Smuzhiyun 
250*4882a593Smuzhiyun 	oaktrail_bl_device = bd;
251*4882a593Smuzhiyun 
252*4882a593Smuzhiyun 	bd->props.brightness = get_backlight_brightness(bd);
253*4882a593Smuzhiyun 	bd->props.power = FB_BLANK_UNBLANK;
254*4882a593Smuzhiyun 	backlight_update_status(bd);
255*4882a593Smuzhiyun 
256*4882a593Smuzhiyun 	return 0;
257*4882a593Smuzhiyun }
258*4882a593Smuzhiyun 
oaktrail_backlight_exit(void)259*4882a593Smuzhiyun static void oaktrail_backlight_exit(void)
260*4882a593Smuzhiyun {
261*4882a593Smuzhiyun 	backlight_device_unregister(oaktrail_bl_device);
262*4882a593Smuzhiyun }
263*4882a593Smuzhiyun 
oaktrail_probe(struct platform_device * pdev)264*4882a593Smuzhiyun static int oaktrail_probe(struct platform_device *pdev)
265*4882a593Smuzhiyun {
266*4882a593Smuzhiyun 	return 0;
267*4882a593Smuzhiyun }
268*4882a593Smuzhiyun 
oaktrail_remove(struct platform_device * pdev)269*4882a593Smuzhiyun static int oaktrail_remove(struct platform_device *pdev)
270*4882a593Smuzhiyun {
271*4882a593Smuzhiyun 	return 0;
272*4882a593Smuzhiyun }
273*4882a593Smuzhiyun 
274*4882a593Smuzhiyun static struct platform_driver oaktrail_driver = {
275*4882a593Smuzhiyun 	.driver = {
276*4882a593Smuzhiyun 		.name = DRIVER_NAME,
277*4882a593Smuzhiyun 	},
278*4882a593Smuzhiyun 	.probe	= oaktrail_probe,
279*4882a593Smuzhiyun 	.remove	= oaktrail_remove,
280*4882a593Smuzhiyun };
281*4882a593Smuzhiyun 
dmi_check_cb(const struct dmi_system_id * id)282*4882a593Smuzhiyun static int dmi_check_cb(const struct dmi_system_id *id)
283*4882a593Smuzhiyun {
284*4882a593Smuzhiyun 	pr_info("Identified model '%s'\n", id->ident);
285*4882a593Smuzhiyun 	return 0;
286*4882a593Smuzhiyun }
287*4882a593Smuzhiyun 
288*4882a593Smuzhiyun static const struct dmi_system_id oaktrail_dmi_table[] __initconst = {
289*4882a593Smuzhiyun 	{
290*4882a593Smuzhiyun 		.ident = "OakTrail platform",
291*4882a593Smuzhiyun 		.matches = {
292*4882a593Smuzhiyun 			DMI_MATCH(DMI_PRODUCT_NAME, "OakTrail platform"),
293*4882a593Smuzhiyun 		},
294*4882a593Smuzhiyun 		.callback = dmi_check_cb
295*4882a593Smuzhiyun 	},
296*4882a593Smuzhiyun 	{ }
297*4882a593Smuzhiyun };
298*4882a593Smuzhiyun MODULE_DEVICE_TABLE(dmi, oaktrail_dmi_table);
299*4882a593Smuzhiyun 
oaktrail_init(void)300*4882a593Smuzhiyun static int __init oaktrail_init(void)
301*4882a593Smuzhiyun {
302*4882a593Smuzhiyun 	int ret;
303*4882a593Smuzhiyun 
304*4882a593Smuzhiyun 	if (acpi_disabled) {
305*4882a593Smuzhiyun 		pr_err("ACPI needs to be enabled for this driver to work!\n");
306*4882a593Smuzhiyun 		return -ENODEV;
307*4882a593Smuzhiyun 	}
308*4882a593Smuzhiyun 
309*4882a593Smuzhiyun 	if (!force && !dmi_check_system(oaktrail_dmi_table)) {
310*4882a593Smuzhiyun 		pr_err("Platform not recognized (You could try the module's force-parameter)");
311*4882a593Smuzhiyun 		return -ENODEV;
312*4882a593Smuzhiyun 	}
313*4882a593Smuzhiyun 
314*4882a593Smuzhiyun 	ret = platform_driver_register(&oaktrail_driver);
315*4882a593Smuzhiyun 	if (ret) {
316*4882a593Smuzhiyun 		pr_warn("Unable to register platform driver\n");
317*4882a593Smuzhiyun 		goto err_driver_reg;
318*4882a593Smuzhiyun 	}
319*4882a593Smuzhiyun 
320*4882a593Smuzhiyun 	oaktrail_device = platform_device_alloc(DRIVER_NAME, -1);
321*4882a593Smuzhiyun 	if (!oaktrail_device) {
322*4882a593Smuzhiyun 		pr_warn("Unable to allocate platform device\n");
323*4882a593Smuzhiyun 		ret = -ENOMEM;
324*4882a593Smuzhiyun 		goto err_device_alloc;
325*4882a593Smuzhiyun 	}
326*4882a593Smuzhiyun 
327*4882a593Smuzhiyun 	ret = platform_device_add(oaktrail_device);
328*4882a593Smuzhiyun 	if (ret) {
329*4882a593Smuzhiyun 		pr_warn("Unable to add platform device\n");
330*4882a593Smuzhiyun 		goto err_device_add;
331*4882a593Smuzhiyun 	}
332*4882a593Smuzhiyun 
333*4882a593Smuzhiyun 	if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
334*4882a593Smuzhiyun 		ret = oaktrail_backlight_init();
335*4882a593Smuzhiyun 		if (ret)
336*4882a593Smuzhiyun 			goto err_backlight;
337*4882a593Smuzhiyun 	}
338*4882a593Smuzhiyun 
339*4882a593Smuzhiyun 	ret = oaktrail_rfkill_init();
340*4882a593Smuzhiyun 	if (ret) {
341*4882a593Smuzhiyun 		pr_warn("Setup rfkill failed\n");
342*4882a593Smuzhiyun 		goto err_rfkill;
343*4882a593Smuzhiyun 	}
344*4882a593Smuzhiyun 
345*4882a593Smuzhiyun 	pr_info("Driver "DRIVER_VERSION" successfully loaded\n");
346*4882a593Smuzhiyun 	return 0;
347*4882a593Smuzhiyun 
348*4882a593Smuzhiyun err_rfkill:
349*4882a593Smuzhiyun 	oaktrail_backlight_exit();
350*4882a593Smuzhiyun err_backlight:
351*4882a593Smuzhiyun 	platform_device_del(oaktrail_device);
352*4882a593Smuzhiyun err_device_add:
353*4882a593Smuzhiyun 	platform_device_put(oaktrail_device);
354*4882a593Smuzhiyun err_device_alloc:
355*4882a593Smuzhiyun 	platform_driver_unregister(&oaktrail_driver);
356*4882a593Smuzhiyun err_driver_reg:
357*4882a593Smuzhiyun 
358*4882a593Smuzhiyun 	return ret;
359*4882a593Smuzhiyun }
360*4882a593Smuzhiyun 
oaktrail_cleanup(void)361*4882a593Smuzhiyun static void __exit oaktrail_cleanup(void)
362*4882a593Smuzhiyun {
363*4882a593Smuzhiyun 	oaktrail_backlight_exit();
364*4882a593Smuzhiyun 	oaktrail_rfkill_cleanup();
365*4882a593Smuzhiyun 	platform_device_unregister(oaktrail_device);
366*4882a593Smuzhiyun 	platform_driver_unregister(&oaktrail_driver);
367*4882a593Smuzhiyun 
368*4882a593Smuzhiyun 	pr_info("Driver unloaded\n");
369*4882a593Smuzhiyun }
370*4882a593Smuzhiyun 
371*4882a593Smuzhiyun module_init(oaktrail_init);
372*4882a593Smuzhiyun module_exit(oaktrail_cleanup);
373*4882a593Smuzhiyun 
374*4882a593Smuzhiyun MODULE_AUTHOR("Yin Kangkai (kangkai.yin@intel.com)");
375*4882a593Smuzhiyun MODULE_DESCRIPTION("Intel Oaktrail Platform ACPI Extras");
376*4882a593Smuzhiyun MODULE_VERSION(DRIVER_VERSION);
377*4882a593Smuzhiyun MODULE_LICENSE("GPL");
378