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