compal-laptop.c 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. /*-*-linux-c-*-*/
  2. /*
  3. Copyright (C) 2008 Cezary Jackiewicz <cezary.jackiewicz (at) gmail.com>
  4. based on MSI driver
  5. Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
  6. This program is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful, but
  11. WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program; if not, write to the Free Software
  16. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  17. 02110-1301, USA.
  18. */
  19. /*
  20. * compal-laptop.c - Compal laptop support.
  21. *
  22. * This driver exports a few files in /sys/devices/platform/compal-laptop/:
  23. * wake_up_XXX Whether or not we listen to such wake up events (rw)
  24. *
  25. * In addition to these platform device attributes the driver
  26. * registers itself in the Linux backlight control, power_supply, rfkill
  27. * and hwmon subsystem and is available to userspace under:
  28. *
  29. * /sys/class/backlight/compal-laptop/
  30. * /sys/class/power_supply/compal-laptop/
  31. * /sys/class/rfkill/rfkillX/
  32. * /sys/class/hwmon/hwmonX/
  33. *
  34. * Notes on the power_supply battery interface:
  35. * - the "minimum" design voltage is *the* design voltage
  36. * - the ambient temperature is the average battery temperature
  37. * and the value is an educated guess (see commented code below)
  38. *
  39. *
  40. * This driver might work on other laptops produced by Compal. If you
  41. * want to try it you can pass force=1 as argument to the module which
  42. * will force it to load even when the DMI data doesn't identify the
  43. * laptop as compatible.
  44. *
  45. * Lots of data available at:
  46. * http://service1.marasst.com/Compal/JHL90_91/Service%20Manual/
  47. * JHL90%20service%20manual-Final-0725.pdf
  48. *
  49. *
  50. *
  51. * Support for the Compal JHL90 added by Roald Frederickx
  52. * (roald.frederickx@gmail.com):
  53. * Driver got large revision. Added functionalities: backlight
  54. * power, wake_on_XXX, a hwmon and power_supply interface.
  55. *
  56. * In case this gets merged into the kernel source: I want to dedicate this
  57. * to Kasper Meerts, the awesome guy who showed me Linux and C!
  58. */
  59. /* NOTE: currently the wake_on_XXX, hwmon and power_supply interfaces are
  60. * only enabled on a JHL90 board until it is verified that they work on the
  61. * other boards too. See the extra_features variable. */
  62. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  63. #include <linux/module.h>
  64. #include <linux/kernel.h>
  65. #include <linux/init.h>
  66. #include <linux/acpi.h>
  67. #include <linux/dmi.h>
  68. #include <linux/backlight.h>
  69. #include <linux/platform_device.h>
  70. #include <linux/rfkill.h>
  71. #include <linux/hwmon.h>
  72. #include <linux/hwmon-sysfs.h>
  73. #include <linux/power_supply.h>
  74. #include <linux/fb.h>
  75. /* ======= */
  76. /* Defines */
  77. /* ======= */
  78. #define DRIVER_NAME "compal-laptop"
  79. #define DRIVER_VERSION "0.2.7"
  80. #define BACKLIGHT_LEVEL_ADDR 0xB9
  81. #define BACKLIGHT_LEVEL_MAX 7
  82. #define BACKLIGHT_STATE_ADDR 0x59
  83. #define BACKLIGHT_STATE_ON_DATA 0xE1
  84. #define BACKLIGHT_STATE_OFF_DATA 0xE2
  85. #define WAKE_UP_ADDR 0xA4
  86. #define WAKE_UP_PME (1 << 0)
  87. #define WAKE_UP_MODEM (1 << 1)
  88. #define WAKE_UP_LAN (1 << 2)
  89. #define WAKE_UP_WLAN (1 << 4)
  90. #define WAKE_UP_KEY (1 << 6)
  91. #define WAKE_UP_MOUSE (1 << 7)
  92. #define WIRELESS_ADDR 0xBB
  93. #define WIRELESS_WLAN (1 << 0)
  94. #define WIRELESS_BT (1 << 1)
  95. #define WIRELESS_WLAN_EXISTS (1 << 2)
  96. #define WIRELESS_BT_EXISTS (1 << 3)
  97. #define WIRELESS_KILLSWITCH (1 << 4)
  98. #define PWM_ADDRESS 0x46
  99. #define PWM_DISABLE_ADDR 0x59
  100. #define PWM_DISABLE_DATA 0xA5
  101. #define PWM_ENABLE_ADDR 0x59
  102. #define PWM_ENABLE_DATA 0xA8
  103. #define FAN_ADDRESS 0x46
  104. #define FAN_DATA 0x81
  105. #define FAN_FULL_ON_CMD 0x59 /* Doesn't seem to work. Just */
  106. #define FAN_FULL_ON_ENABLE 0x76 /* force the pwm signal to its */
  107. #define FAN_FULL_ON_DISABLE 0x77 /* maximum value instead */
  108. #define TEMP_CPU 0xB0
  109. #define TEMP_CPU_LOCAL 0xB1
  110. #define TEMP_CPU_DTS 0xB5
  111. #define TEMP_NORTHBRIDGE 0xB6
  112. #define TEMP_VGA 0xB4
  113. #define TEMP_SKIN 0xB2
  114. #define BAT_MANUFACTURER_NAME_ADDR 0x10
  115. #define BAT_MANUFACTURER_NAME_LEN 9
  116. #define BAT_MODEL_NAME_ADDR 0x19
  117. #define BAT_MODEL_NAME_LEN 6
  118. #define BAT_SERIAL_NUMBER_ADDR 0xC4
  119. #define BAT_SERIAL_NUMBER_LEN 5
  120. #define BAT_CHARGE_NOW 0xC2
  121. #define BAT_CHARGE_DESIGN 0xCA
  122. #define BAT_VOLTAGE_NOW 0xC6
  123. #define BAT_VOLTAGE_DESIGN 0xC8
  124. #define BAT_CURRENT_NOW 0xD0
  125. #define BAT_CURRENT_AVG 0xD2
  126. #define BAT_POWER 0xD4
  127. #define BAT_CAPACITY 0xCE
  128. #define BAT_TEMP 0xD6
  129. #define BAT_TEMP_AVG 0xD7
  130. #define BAT_STATUS0 0xC1
  131. #define BAT_STATUS1 0xF0
  132. #define BAT_STATUS2 0xF1
  133. #define BAT_STOP_CHARGE1 0xF2
  134. #define BAT_STOP_CHARGE2 0xF3
  135. #define BAT_S0_DISCHARGE (1 << 0)
  136. #define BAT_S0_DISCHRG_CRITICAL (1 << 2)
  137. #define BAT_S0_LOW (1 << 3)
  138. #define BAT_S0_CHARGING (1 << 1)
  139. #define BAT_S0_AC (1 << 7)
  140. #define BAT_S1_EXISTS (1 << 0)
  141. #define BAT_S1_FULL (1 << 1)
  142. #define BAT_S1_EMPTY (1 << 2)
  143. #define BAT_S1_LiION_OR_NiMH (1 << 7)
  144. #define BAT_S2_LOW_LOW (1 << 0)
  145. #define BAT_STOP_CHRG1_BAD_CELL (1 << 1)
  146. #define BAT_STOP_CHRG1_COMM_FAIL (1 << 2)
  147. #define BAT_STOP_CHRG1_OVERVOLTAGE (1 << 6)
  148. #define BAT_STOP_CHRG1_OVERTEMPERATURE (1 << 7)
  149. /* ======= */
  150. /* Structs */
  151. /* ======= */
  152. struct compal_data{
  153. /* Fan control */
  154. struct device *hwmon_dev;
  155. int pwm_enable; /* 0:full on, 1:set by pwm1, 2:control by moterboard */
  156. unsigned char curr_pwm;
  157. /* Power supply */
  158. struct power_supply psy;
  159. struct power_supply_info psy_info;
  160. char bat_model_name[BAT_MODEL_NAME_LEN + 1];
  161. char bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN + 1];
  162. char bat_serial_number[BAT_SERIAL_NUMBER_LEN + 1];
  163. };
  164. /* =============== */
  165. /* General globals */
  166. /* =============== */
  167. static bool force;
  168. module_param(force, bool, 0);
  169. MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
  170. /* Support for the wake_on_XXX, hwmon and power_supply interface. Currently
  171. * only gets enabled on a JHL90 board. Might work with the others too */
  172. static bool extra_features;
  173. /* Nasty stuff. For some reason the fan control is very un-linear. I've
  174. * come up with these values by looping through the possible inputs and
  175. * watching the output of address 0x4F (do an ec_transaction writing 0x33
  176. * into 0x4F and read a few bytes from the output, like so:
  177. * u8 writeData = 0x33;
  178. * ec_transaction(0x4F, &writeData, 1, buffer, 32);
  179. * That address is labeled "fan1 table information" in the service manual.
  180. * It should be clear which value in 'buffer' changes). This seems to be
  181. * related to fan speed. It isn't a proper 'realtime' fan speed value
  182. * though, because physically stopping or speeding up the fan doesn't
  183. * change it. It might be the average voltage or current of the pwm output.
  184. * Nevertheless, it is more fine-grained than the actual RPM reading */
  185. static const unsigned char pwm_lookup_table[256] = {
  186. 0, 0, 0, 1, 1, 1, 2, 253, 254, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6,
  187. 7, 7, 7, 8, 86, 86, 9, 9, 9, 10, 10, 10, 11, 92, 92, 12, 12, 95,
  188. 13, 66, 66, 14, 14, 98, 15, 15, 15, 16, 16, 67, 17, 17, 72, 18, 70,
  189. 75, 19, 90, 90, 73, 73, 73, 21, 21, 91, 91, 91, 96, 23, 94, 94, 94,
  190. 94, 94, 94, 94, 94, 94, 94, 141, 141, 238, 223, 192, 139, 139, 139,
  191. 139, 139, 142, 142, 142, 142, 142, 78, 78, 78, 78, 78, 76, 76, 76,
  192. 76, 76, 79, 79, 79, 79, 79, 79, 79, 20, 20, 20, 20, 20, 22, 22, 22,
  193. 22, 22, 24, 24, 24, 24, 24, 24, 219, 219, 219, 219, 219, 219, 219,
  194. 219, 27, 27, 188, 188, 28, 28, 28, 29, 186, 186, 186, 186, 186,
  195. 186, 186, 186, 186, 186, 31, 31, 31, 31, 31, 32, 32, 32, 41, 33,
  196. 33, 33, 33, 33, 252, 252, 34, 34, 34, 43, 35, 35, 35, 36, 36, 38,
  197. 206, 206, 206, 206, 206, 206, 206, 206, 206, 37, 37, 37, 46, 46,
  198. 47, 47, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 48, 48,
  199. 48, 48, 48, 40, 40, 40, 49, 42, 42, 42, 42, 42, 42, 42, 42, 44,
  200. 189, 189, 189, 189, 54, 54, 45, 45, 45, 45, 45, 45, 45, 45, 251,
  201. 191, 199, 199, 199, 199, 199, 215, 215, 215, 215, 187, 187, 187,
  202. 187, 187, 193, 50
  203. };
  204. /* ========================= */
  205. /* Hardware access functions */
  206. /* ========================= */
  207. /* General access */
  208. static u8 ec_read_u8(u8 addr)
  209. {
  210. u8 value;
  211. ec_read(addr, &value);
  212. return value;
  213. }
  214. static s8 ec_read_s8(u8 addr)
  215. {
  216. return (s8)ec_read_u8(addr);
  217. }
  218. static u16 ec_read_u16(u8 addr)
  219. {
  220. int hi, lo;
  221. lo = ec_read_u8(addr);
  222. hi = ec_read_u8(addr + 1);
  223. return (hi << 8) + lo;
  224. }
  225. static s16 ec_read_s16(u8 addr)
  226. {
  227. return (s16) ec_read_u16(addr);
  228. }
  229. static void ec_read_sequence(u8 addr, u8 *buf, int len)
  230. {
  231. int i;
  232. for (i = 0; i < len; i++)
  233. ec_read(addr + i, buf + i);
  234. }
  235. /* Backlight access */
  236. static int set_backlight_level(int level)
  237. {
  238. if (level < 0 || level > BACKLIGHT_LEVEL_MAX)
  239. return -EINVAL;
  240. ec_write(BACKLIGHT_LEVEL_ADDR, level);
  241. return 0;
  242. }
  243. static int get_backlight_level(void)
  244. {
  245. return (int) ec_read_u8(BACKLIGHT_LEVEL_ADDR);
  246. }
  247. static void set_backlight_state(bool on)
  248. {
  249. u8 data = on ? BACKLIGHT_STATE_ON_DATA : BACKLIGHT_STATE_OFF_DATA;
  250. ec_transaction(BACKLIGHT_STATE_ADDR, &data, 1, NULL, 0);
  251. }
  252. /* Fan control access */
  253. static void pwm_enable_control(void)
  254. {
  255. unsigned char writeData = PWM_ENABLE_DATA;
  256. ec_transaction(PWM_ENABLE_ADDR, &writeData, 1, NULL, 0);
  257. }
  258. static void pwm_disable_control(void)
  259. {
  260. unsigned char writeData = PWM_DISABLE_DATA;
  261. ec_transaction(PWM_DISABLE_ADDR, &writeData, 1, NULL, 0);
  262. }
  263. static void set_pwm(int pwm)
  264. {
  265. ec_transaction(PWM_ADDRESS, &pwm_lookup_table[pwm], 1, NULL, 0);
  266. }
  267. static int get_fan_rpm(void)
  268. {
  269. u8 value, data = FAN_DATA;
  270. ec_transaction(FAN_ADDRESS, &data, 1, &value, 1);
  271. return 100 * (int)value;
  272. }
  273. /* =================== */
  274. /* Interface functions */
  275. /* =================== */
  276. /* Backlight interface */
  277. static int bl_get_brightness(struct backlight_device *b)
  278. {
  279. return get_backlight_level();
  280. }
  281. static int bl_update_status(struct backlight_device *b)
  282. {
  283. int ret = set_backlight_level(b->props.brightness);
  284. if (ret)
  285. return ret;
  286. set_backlight_state((b->props.power == FB_BLANK_UNBLANK)
  287. && !(b->props.state & BL_CORE_SUSPENDED)
  288. && !(b->props.state & BL_CORE_FBBLANK));
  289. return 0;
  290. }
  291. static const struct backlight_ops compalbl_ops = {
  292. .get_brightness = bl_get_brightness,
  293. .update_status = bl_update_status,
  294. };
  295. /* Wireless interface */
  296. static int compal_rfkill_set(void *data, bool blocked)
  297. {
  298. unsigned long radio = (unsigned long) data;
  299. u8 result = ec_read_u8(WIRELESS_ADDR);
  300. u8 value;
  301. if (!blocked)
  302. value = (u8) (result | radio);
  303. else
  304. value = (u8) (result & ~radio);
  305. ec_write(WIRELESS_ADDR, value);
  306. return 0;
  307. }
  308. static void compal_rfkill_poll(struct rfkill *rfkill, void *data)
  309. {
  310. u8 result = ec_read_u8(WIRELESS_ADDR);
  311. bool hw_blocked = !(result & WIRELESS_KILLSWITCH);
  312. rfkill_set_hw_state(rfkill, hw_blocked);
  313. }
  314. static const struct rfkill_ops compal_rfkill_ops = {
  315. .poll = compal_rfkill_poll,
  316. .set_block = compal_rfkill_set,
  317. };
  318. /* Wake_up interface */
  319. #define SIMPLE_MASKED_STORE_SHOW(NAME, ADDR, MASK) \
  320. static ssize_t NAME##_show(struct device *dev, \
  321. struct device_attribute *attr, char *buf) \
  322. { \
  323. return sprintf(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0)); \
  324. } \
  325. static ssize_t NAME##_store(struct device *dev, \
  326. struct device_attribute *attr, const char *buf, size_t count) \
  327. { \
  328. int state; \
  329. u8 old_val = ec_read_u8(ADDR); \
  330. if (sscanf(buf, "%d", &state) != 1 || (state < 0 || state > 1)) \
  331. return -EINVAL; \
  332. ec_write(ADDR, state ? (old_val | MASK) : (old_val & ~MASK)); \
  333. return count; \
  334. }
  335. SIMPLE_MASKED_STORE_SHOW(wake_up_pme, WAKE_UP_ADDR, WAKE_UP_PME)
  336. SIMPLE_MASKED_STORE_SHOW(wake_up_modem, WAKE_UP_ADDR, WAKE_UP_MODEM)
  337. SIMPLE_MASKED_STORE_SHOW(wake_up_lan, WAKE_UP_ADDR, WAKE_UP_LAN)
  338. SIMPLE_MASKED_STORE_SHOW(wake_up_wlan, WAKE_UP_ADDR, WAKE_UP_WLAN)
  339. SIMPLE_MASKED_STORE_SHOW(wake_up_key, WAKE_UP_ADDR, WAKE_UP_KEY)
  340. SIMPLE_MASKED_STORE_SHOW(wake_up_mouse, WAKE_UP_ADDR, WAKE_UP_MOUSE)
  341. /* General hwmon interface */
  342. static ssize_t hwmon_name_show(struct device *dev,
  343. struct device_attribute *attr, char *buf)
  344. {
  345. return sprintf(buf, "%s\n", DRIVER_NAME);
  346. }
  347. /* Fan control interface */
  348. static ssize_t pwm_enable_show(struct device *dev,
  349. struct device_attribute *attr, char *buf)
  350. {
  351. struct compal_data *data = dev_get_drvdata(dev);
  352. return sprintf(buf, "%d\n", data->pwm_enable);
  353. }
  354. static ssize_t pwm_enable_store(struct device *dev,
  355. struct device_attribute *attr, const char *buf, size_t count)
  356. {
  357. struct compal_data *data = dev_get_drvdata(dev);
  358. long val;
  359. int err;
  360. err = strict_strtol(buf, 10, &val);
  361. if (err)
  362. return err;
  363. if (val < 0)
  364. return -EINVAL;
  365. data->pwm_enable = val;
  366. switch (val) {
  367. case 0: /* Full speed */
  368. pwm_enable_control();
  369. set_pwm(255);
  370. break;
  371. case 1: /* As set by pwm1 */
  372. pwm_enable_control();
  373. set_pwm(data->curr_pwm);
  374. break;
  375. default: /* Control by motherboard */
  376. pwm_disable_control();
  377. break;
  378. }
  379. return count;
  380. }
  381. static ssize_t pwm_show(struct device *dev, struct device_attribute *attr,
  382. char *buf)
  383. {
  384. struct compal_data *data = dev_get_drvdata(dev);
  385. return sprintf(buf, "%hhu\n", data->curr_pwm);
  386. }
  387. static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
  388. const char *buf, size_t count)
  389. {
  390. struct compal_data *data = dev_get_drvdata(dev);
  391. long val;
  392. int err;
  393. err = strict_strtol(buf, 10, &val);
  394. if (err)
  395. return err;
  396. if (val < 0 || val > 255)
  397. return -EINVAL;
  398. data->curr_pwm = val;
  399. if (data->pwm_enable != 1)
  400. return count;
  401. set_pwm(val);
  402. return count;
  403. }
  404. static ssize_t fan_show(struct device *dev, struct device_attribute *attr,
  405. char *buf)
  406. {
  407. return sprintf(buf, "%d\n", get_fan_rpm());
  408. }
  409. /* Temperature interface */
  410. #define TEMPERATURE_SHOW_TEMP_AND_LABEL(POSTFIX, ADDRESS, LABEL) \
  411. static ssize_t temp_##POSTFIX(struct device *dev, \
  412. struct device_attribute *attr, char *buf) \
  413. { \
  414. return sprintf(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS)); \
  415. } \
  416. static ssize_t label_##POSTFIX(struct device *dev, \
  417. struct device_attribute *attr, char *buf) \
  418. { \
  419. return sprintf(buf, "%s\n", LABEL); \
  420. }
  421. /* Labels as in service guide */
  422. TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu, TEMP_CPU, "CPU_TEMP");
  423. TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_local, TEMP_CPU_LOCAL, "CPU_TEMP_LOCAL");
  424. TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_DTS, TEMP_CPU_DTS, "CPU_DTS");
  425. TEMPERATURE_SHOW_TEMP_AND_LABEL(northbridge,TEMP_NORTHBRIDGE,"NorthBridge");
  426. TEMPERATURE_SHOW_TEMP_AND_LABEL(vga, TEMP_VGA, "VGA_TEMP");
  427. TEMPERATURE_SHOW_TEMP_AND_LABEL(SKIN, TEMP_SKIN, "SKIN_TEMP90");
  428. /* Power supply interface */
  429. static int bat_status(void)
  430. {
  431. u8 status0 = ec_read_u8(BAT_STATUS0);
  432. u8 status1 = ec_read_u8(BAT_STATUS1);
  433. if (status0 & BAT_S0_CHARGING)
  434. return POWER_SUPPLY_STATUS_CHARGING;
  435. if (status0 & BAT_S0_DISCHARGE)
  436. return POWER_SUPPLY_STATUS_DISCHARGING;
  437. if (status1 & BAT_S1_FULL)
  438. return POWER_SUPPLY_STATUS_FULL;
  439. return POWER_SUPPLY_STATUS_NOT_CHARGING;
  440. }
  441. static int bat_health(void)
  442. {
  443. u8 status = ec_read_u8(BAT_STOP_CHARGE1);
  444. if (status & BAT_STOP_CHRG1_OVERTEMPERATURE)
  445. return POWER_SUPPLY_HEALTH_OVERHEAT;
  446. if (status & BAT_STOP_CHRG1_OVERVOLTAGE)
  447. return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
  448. if (status & BAT_STOP_CHRG1_BAD_CELL)
  449. return POWER_SUPPLY_HEALTH_DEAD;
  450. if (status & BAT_STOP_CHRG1_COMM_FAIL)
  451. return POWER_SUPPLY_HEALTH_UNKNOWN;
  452. return POWER_SUPPLY_HEALTH_GOOD;
  453. }
  454. static int bat_is_present(void)
  455. {
  456. u8 status = ec_read_u8(BAT_STATUS2);
  457. return ((status & BAT_S1_EXISTS) != 0);
  458. }
  459. static int bat_technology(void)
  460. {
  461. u8 status = ec_read_u8(BAT_STATUS1);
  462. if (status & BAT_S1_LiION_OR_NiMH)
  463. return POWER_SUPPLY_TECHNOLOGY_LION;
  464. return POWER_SUPPLY_TECHNOLOGY_NiMH;
  465. }
  466. static int bat_capacity_level(void)
  467. {
  468. u8 status0 = ec_read_u8(BAT_STATUS0);
  469. u8 status1 = ec_read_u8(BAT_STATUS1);
  470. u8 status2 = ec_read_u8(BAT_STATUS2);
  471. if (status0 & BAT_S0_DISCHRG_CRITICAL
  472. || status1 & BAT_S1_EMPTY
  473. || status2 & BAT_S2_LOW_LOW)
  474. return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
  475. if (status0 & BAT_S0_LOW)
  476. return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
  477. if (status1 & BAT_S1_FULL)
  478. return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
  479. return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
  480. }
  481. static int bat_get_property(struct power_supply *psy,
  482. enum power_supply_property psp,
  483. union power_supply_propval *val)
  484. {
  485. struct compal_data *data;
  486. data = container_of(psy, struct compal_data, psy);
  487. switch (psp) {
  488. case POWER_SUPPLY_PROP_STATUS:
  489. val->intval = bat_status();
  490. break;
  491. case POWER_SUPPLY_PROP_HEALTH:
  492. val->intval = bat_health();
  493. break;
  494. case POWER_SUPPLY_PROP_PRESENT:
  495. val->intval = bat_is_present();
  496. break;
  497. case POWER_SUPPLY_PROP_TECHNOLOGY:
  498. val->intval = bat_technology();
  499. break;
  500. case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: /* THE design voltage... */
  501. val->intval = ec_read_u16(BAT_VOLTAGE_DESIGN) * 1000;
  502. break;
  503. case POWER_SUPPLY_PROP_VOLTAGE_NOW:
  504. val->intval = ec_read_u16(BAT_VOLTAGE_NOW) * 1000;
  505. break;
  506. case POWER_SUPPLY_PROP_CURRENT_NOW:
  507. val->intval = ec_read_s16(BAT_CURRENT_NOW) * 1000;
  508. break;
  509. case POWER_SUPPLY_PROP_CURRENT_AVG:
  510. val->intval = ec_read_s16(BAT_CURRENT_AVG) * 1000;
  511. break;
  512. case POWER_SUPPLY_PROP_POWER_NOW:
  513. val->intval = ec_read_u8(BAT_POWER) * 1000000;
  514. break;
  515. case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
  516. val->intval = ec_read_u16(BAT_CHARGE_DESIGN) * 1000;
  517. break;
  518. case POWER_SUPPLY_PROP_CHARGE_NOW:
  519. val->intval = ec_read_u16(BAT_CHARGE_NOW) * 1000;
  520. break;
  521. case POWER_SUPPLY_PROP_CAPACITY:
  522. val->intval = ec_read_u8(BAT_CAPACITY);
  523. break;
  524. case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
  525. val->intval = bat_capacity_level();
  526. break;
  527. /* It smees that BAT_TEMP_AVG is a (2's complement?) value showing
  528. * the number of degrees, whereas BAT_TEMP is somewhat more
  529. * complicated. It looks like this is a negative nember with a
  530. * 100/256 divider and an offset of 222. Both were determined
  531. * experimentally by comparing BAT_TEMP and BAT_TEMP_AVG. */
  532. case POWER_SUPPLY_PROP_TEMP:
  533. val->intval = ((222 - (int)ec_read_u8(BAT_TEMP)) * 1000) >> 8;
  534. break;
  535. case POWER_SUPPLY_PROP_TEMP_AMBIENT: /* Ambient, Avg, ... same thing */
  536. val->intval = ec_read_s8(BAT_TEMP_AVG) * 10;
  537. break;
  538. /* Neither the model name nor manufacturer name work for me. */
  539. case POWER_SUPPLY_PROP_MODEL_NAME:
  540. val->strval = data->bat_model_name;
  541. break;
  542. case POWER_SUPPLY_PROP_MANUFACTURER:
  543. val->strval = data->bat_manufacturer_name;
  544. break;
  545. case POWER_SUPPLY_PROP_SERIAL_NUMBER:
  546. val->strval = data->bat_serial_number;
  547. break;
  548. default:
  549. break;
  550. }
  551. return 0;
  552. }
  553. /* ============== */
  554. /* Driver Globals */
  555. /* ============== */
  556. static DEVICE_ATTR(wake_up_pme,
  557. 0644, wake_up_pme_show, wake_up_pme_store);
  558. static DEVICE_ATTR(wake_up_modem,
  559. 0644, wake_up_modem_show, wake_up_modem_store);
  560. static DEVICE_ATTR(wake_up_lan,
  561. 0644, wake_up_lan_show, wake_up_lan_store);
  562. static DEVICE_ATTR(wake_up_wlan,
  563. 0644, wake_up_wlan_show, wake_up_wlan_store);
  564. static DEVICE_ATTR(wake_up_key,
  565. 0644, wake_up_key_show, wake_up_key_store);
  566. static DEVICE_ATTR(wake_up_mouse,
  567. 0644, wake_up_mouse_show, wake_up_mouse_store);
  568. static SENSOR_DEVICE_ATTR(name, S_IRUGO, hwmon_name_show, NULL, 1);
  569. static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, fan_show, NULL, 1);
  570. static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, temp_cpu, NULL, 1);
  571. static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, temp_cpu_local, NULL, 1);
  572. static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, temp_cpu_DTS, NULL, 1);
  573. static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, temp_northbridge, NULL, 1);
  574. static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, temp_vga, NULL, 1);
  575. static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, temp_SKIN, NULL, 1);
  576. static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, label_cpu, NULL, 1);
  577. static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, label_cpu_local, NULL, 1);
  578. static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, label_cpu_DTS, NULL, 1);
  579. static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, label_northbridge, NULL, 1);
  580. static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, label_vga, NULL, 1);
  581. static SENSOR_DEVICE_ATTR(temp6_label, S_IRUGO, label_SKIN, NULL, 1);
  582. static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, pwm_show, pwm_store, 1);
  583. static SENSOR_DEVICE_ATTR(pwm1_enable,
  584. S_IRUGO | S_IWUSR, pwm_enable_show, pwm_enable_store, 0);
  585. static struct attribute *compal_attributes[] = {
  586. &dev_attr_wake_up_pme.attr,
  587. &dev_attr_wake_up_modem.attr,
  588. &dev_attr_wake_up_lan.attr,
  589. &dev_attr_wake_up_wlan.attr,
  590. &dev_attr_wake_up_key.attr,
  591. &dev_attr_wake_up_mouse.attr,
  592. /* Maybe put the sensor-stuff in a separate hwmon-driver? That way,
  593. * the hwmon sysfs won't be cluttered with the above files. */
  594. &sensor_dev_attr_name.dev_attr.attr,
  595. &sensor_dev_attr_pwm1_enable.dev_attr.attr,
  596. &sensor_dev_attr_pwm1.dev_attr.attr,
  597. &sensor_dev_attr_fan1_input.dev_attr.attr,
  598. &sensor_dev_attr_temp1_input.dev_attr.attr,
  599. &sensor_dev_attr_temp2_input.dev_attr.attr,
  600. &sensor_dev_attr_temp3_input.dev_attr.attr,
  601. &sensor_dev_attr_temp4_input.dev_attr.attr,
  602. &sensor_dev_attr_temp5_input.dev_attr.attr,
  603. &sensor_dev_attr_temp6_input.dev_attr.attr,
  604. &sensor_dev_attr_temp1_label.dev_attr.attr,
  605. &sensor_dev_attr_temp2_label.dev_attr.attr,
  606. &sensor_dev_attr_temp3_label.dev_attr.attr,
  607. &sensor_dev_attr_temp4_label.dev_attr.attr,
  608. &sensor_dev_attr_temp5_label.dev_attr.attr,
  609. &sensor_dev_attr_temp6_label.dev_attr.attr,
  610. NULL
  611. };
  612. static struct attribute_group compal_attribute_group = {
  613. .attrs = compal_attributes
  614. };
  615. static int __devinit compal_probe(struct platform_device *);
  616. static int __devexit compal_remove(struct platform_device *);
  617. static struct platform_driver compal_driver = {
  618. .driver = {
  619. .name = DRIVER_NAME,
  620. .owner = THIS_MODULE,
  621. },
  622. .probe = compal_probe,
  623. .remove = __devexit_p(compal_remove)
  624. };
  625. static enum power_supply_property compal_bat_properties[] = {
  626. POWER_SUPPLY_PROP_STATUS,
  627. POWER_SUPPLY_PROP_HEALTH,
  628. POWER_SUPPLY_PROP_PRESENT,
  629. POWER_SUPPLY_PROP_TECHNOLOGY,
  630. POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
  631. POWER_SUPPLY_PROP_VOLTAGE_NOW,
  632. POWER_SUPPLY_PROP_CURRENT_NOW,
  633. POWER_SUPPLY_PROP_CURRENT_AVG,
  634. POWER_SUPPLY_PROP_POWER_NOW,
  635. POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
  636. POWER_SUPPLY_PROP_CHARGE_NOW,
  637. POWER_SUPPLY_PROP_CAPACITY,
  638. POWER_SUPPLY_PROP_CAPACITY_LEVEL,
  639. POWER_SUPPLY_PROP_TEMP,
  640. POWER_SUPPLY_PROP_TEMP_AMBIENT,
  641. POWER_SUPPLY_PROP_MODEL_NAME,
  642. POWER_SUPPLY_PROP_MANUFACTURER,
  643. POWER_SUPPLY_PROP_SERIAL_NUMBER,
  644. };
  645. static struct backlight_device *compalbl_device;
  646. static struct platform_device *compal_device;
  647. static struct rfkill *wifi_rfkill;
  648. static struct rfkill *bt_rfkill;
  649. /* =================================== */
  650. /* Initialization & clean-up functions */
  651. /* =================================== */
  652. static int dmi_check_cb(const struct dmi_system_id *id)
  653. {
  654. pr_info("Identified laptop model '%s'\n", id->ident);
  655. extra_features = false;
  656. return 1;
  657. }
  658. static int dmi_check_cb_extra(const struct dmi_system_id *id)
  659. {
  660. pr_info("Identified laptop model '%s', enabling extra features\n",
  661. id->ident);
  662. extra_features = true;
  663. return 1;
  664. }
  665. static struct dmi_system_id __initdata compal_dmi_table[] = {
  666. {
  667. .ident = "FL90/IFL90",
  668. .matches = {
  669. DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
  670. DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
  671. },
  672. .callback = dmi_check_cb
  673. },
  674. {
  675. .ident = "FL90/IFL90",
  676. .matches = {
  677. DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
  678. DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
  679. },
  680. .callback = dmi_check_cb
  681. },
  682. {
  683. .ident = "FL91/IFL91",
  684. .matches = {
  685. DMI_MATCH(DMI_BOARD_NAME, "IFL91"),
  686. DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
  687. },
  688. .callback = dmi_check_cb
  689. },
  690. {
  691. .ident = "FL92/JFL92",
  692. .matches = {
  693. DMI_MATCH(DMI_BOARD_NAME, "JFL92"),
  694. DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
  695. },
  696. .callback = dmi_check_cb
  697. },
  698. {
  699. .ident = "FT00/IFT00",
  700. .matches = {
  701. DMI_MATCH(DMI_BOARD_NAME, "IFT00"),
  702. DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
  703. },
  704. .callback = dmi_check_cb
  705. },
  706. {
  707. .ident = "Dell Mini 9",
  708. .matches = {
  709. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  710. DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"),
  711. },
  712. .callback = dmi_check_cb
  713. },
  714. {
  715. .ident = "Dell Mini 10",
  716. .matches = {
  717. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  718. DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"),
  719. },
  720. .callback = dmi_check_cb
  721. },
  722. {
  723. .ident = "Dell Mini 10v",
  724. .matches = {
  725. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  726. DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"),
  727. },
  728. .callback = dmi_check_cb
  729. },
  730. {
  731. .ident = "Dell Mini 1012",
  732. .matches = {
  733. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  734. DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"),
  735. },
  736. .callback = dmi_check_cb
  737. },
  738. {
  739. .ident = "Dell Inspiron 11z",
  740. .matches = {
  741. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  742. DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"),
  743. },
  744. .callback = dmi_check_cb
  745. },
  746. {
  747. .ident = "Dell Mini 12",
  748. .matches = {
  749. DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
  750. DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"),
  751. },
  752. .callback = dmi_check_cb
  753. },
  754. {
  755. .ident = "JHL90",
  756. .matches = {
  757. DMI_MATCH(DMI_BOARD_NAME, "JHL90"),
  758. DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
  759. },
  760. .callback = dmi_check_cb_extra
  761. },
  762. {
  763. .ident = "KHLB2",
  764. .matches = {
  765. DMI_MATCH(DMI_BOARD_NAME, "KHLB2"),
  766. DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
  767. },
  768. .callback = dmi_check_cb_extra
  769. },
  770. { }
  771. };
  772. MODULE_DEVICE_TABLE(dmi, compal_dmi_table);
  773. static void initialize_power_supply_data(struct compal_data *data)
  774. {
  775. data->psy.name = DRIVER_NAME;
  776. data->psy.type = POWER_SUPPLY_TYPE_BATTERY;
  777. data->psy.properties = compal_bat_properties;
  778. data->psy.num_properties = ARRAY_SIZE(compal_bat_properties);
  779. data->psy.get_property = bat_get_property;
  780. ec_read_sequence(BAT_MANUFACTURER_NAME_ADDR,
  781. data->bat_manufacturer_name,
  782. BAT_MANUFACTURER_NAME_LEN);
  783. data->bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN] = 0;
  784. ec_read_sequence(BAT_MODEL_NAME_ADDR,
  785. data->bat_model_name,
  786. BAT_MODEL_NAME_LEN);
  787. data->bat_model_name[BAT_MODEL_NAME_LEN] = 0;
  788. scnprintf(data->bat_serial_number, BAT_SERIAL_NUMBER_LEN + 1, "%d",
  789. ec_read_u16(BAT_SERIAL_NUMBER_ADDR));
  790. }
  791. static void initialize_fan_control_data(struct compal_data *data)
  792. {
  793. data->pwm_enable = 2; /* Keep motherboard in control for now */
  794. data->curr_pwm = 255; /* Try not to cause a CPU_on_fire exception
  795. if we take over... */
  796. }
  797. static int setup_rfkill(void)
  798. {
  799. int ret;
  800. wifi_rfkill = rfkill_alloc("compal-wifi", &compal_device->dev,
  801. RFKILL_TYPE_WLAN, &compal_rfkill_ops,
  802. (void *) WIRELESS_WLAN);
  803. if (!wifi_rfkill)
  804. return -ENOMEM;
  805. ret = rfkill_register(wifi_rfkill);
  806. if (ret)
  807. goto err_wifi;
  808. bt_rfkill = rfkill_alloc("compal-bluetooth", &compal_device->dev,
  809. RFKILL_TYPE_BLUETOOTH, &compal_rfkill_ops,
  810. (void *) WIRELESS_BT);
  811. if (!bt_rfkill) {
  812. ret = -ENOMEM;
  813. goto err_allocate_bt;
  814. }
  815. ret = rfkill_register(bt_rfkill);
  816. if (ret)
  817. goto err_register_bt;
  818. return 0;
  819. err_register_bt:
  820. rfkill_destroy(bt_rfkill);
  821. err_allocate_bt:
  822. rfkill_unregister(wifi_rfkill);
  823. err_wifi:
  824. rfkill_destroy(wifi_rfkill);
  825. return ret;
  826. }
  827. static int __init compal_init(void)
  828. {
  829. int ret;
  830. if (acpi_disabled) {
  831. pr_err("ACPI needs to be enabled for this driver to work!\n");
  832. return -ENODEV;
  833. }
  834. if (!force && !dmi_check_system(compal_dmi_table)) {
  835. pr_err("Motherboard not recognized (You could try the module's force-parameter)\n");
  836. return -ENODEV;
  837. }
  838. if (!acpi_video_backlight_support()) {
  839. struct backlight_properties props;
  840. memset(&props, 0, sizeof(struct backlight_properties));
  841. props.type = BACKLIGHT_PLATFORM;
  842. props.max_brightness = BACKLIGHT_LEVEL_MAX;
  843. compalbl_device = backlight_device_register(DRIVER_NAME,
  844. NULL, NULL,
  845. &compalbl_ops,
  846. &props);
  847. if (IS_ERR(compalbl_device))
  848. return PTR_ERR(compalbl_device);
  849. }
  850. ret = platform_driver_register(&compal_driver);
  851. if (ret)
  852. goto err_backlight;
  853. compal_device = platform_device_alloc(DRIVER_NAME, -1);
  854. if (!compal_device) {
  855. ret = -ENOMEM;
  856. goto err_platform_driver;
  857. }
  858. ret = platform_device_add(compal_device); /* This calls compal_probe */
  859. if (ret)
  860. goto err_platform_device;
  861. ret = setup_rfkill();
  862. if (ret)
  863. goto err_rfkill;
  864. pr_info("Driver " DRIVER_VERSION " successfully loaded\n");
  865. return 0;
  866. err_rfkill:
  867. platform_device_del(compal_device);
  868. err_platform_device:
  869. platform_device_put(compal_device);
  870. err_platform_driver:
  871. platform_driver_unregister(&compal_driver);
  872. err_backlight:
  873. backlight_device_unregister(compalbl_device);
  874. return ret;
  875. }
  876. static int __devinit compal_probe(struct platform_device *pdev)
  877. {
  878. int err;
  879. struct compal_data *data;
  880. if (!extra_features)
  881. return 0;
  882. /* Fan control */
  883. data = kzalloc(sizeof(struct compal_data), GFP_KERNEL);
  884. if (!data)
  885. return -ENOMEM;
  886. initialize_fan_control_data(data);
  887. err = sysfs_create_group(&pdev->dev.kobj, &compal_attribute_group);
  888. if (err) {
  889. kfree(data);
  890. return err;
  891. }
  892. data->hwmon_dev = hwmon_device_register(&pdev->dev);
  893. if (IS_ERR(data->hwmon_dev)) {
  894. err = PTR_ERR(data->hwmon_dev);
  895. sysfs_remove_group(&pdev->dev.kobj,
  896. &compal_attribute_group);
  897. kfree(data);
  898. return err;
  899. }
  900. /* Power supply */
  901. initialize_power_supply_data(data);
  902. err = power_supply_register(&compal_device->dev, &data->psy);
  903. if (err < 0) {
  904. hwmon_device_unregister(data->hwmon_dev);
  905. sysfs_remove_group(&pdev->dev.kobj,
  906. &compal_attribute_group);
  907. kfree(data);
  908. }
  909. platform_set_drvdata(pdev, data);
  910. return 0;
  911. }
  912. static void __exit compal_cleanup(void)
  913. {
  914. platform_device_unregister(compal_device);
  915. platform_driver_unregister(&compal_driver);
  916. backlight_device_unregister(compalbl_device);
  917. rfkill_unregister(wifi_rfkill);
  918. rfkill_unregister(bt_rfkill);
  919. rfkill_destroy(wifi_rfkill);
  920. rfkill_destroy(bt_rfkill);
  921. pr_info("Driver unloaded\n");
  922. }
  923. static int __devexit compal_remove(struct platform_device *pdev)
  924. {
  925. struct compal_data *data;
  926. if (!extra_features)
  927. return 0;
  928. pr_info("Unloading: resetting fan control to motherboard\n");
  929. pwm_disable_control();
  930. data = platform_get_drvdata(pdev);
  931. hwmon_device_unregister(data->hwmon_dev);
  932. power_supply_unregister(&data->psy);
  933. platform_set_drvdata(pdev, NULL);
  934. kfree(data);
  935. sysfs_remove_group(&pdev->dev.kobj, &compal_attribute_group);
  936. return 0;
  937. }
  938. module_init(compal_init);
  939. module_exit(compal_cleanup);
  940. MODULE_AUTHOR("Cezary Jackiewicz");
  941. MODULE_AUTHOR("Roald Frederickx (roald.frederickx@gmail.com)");
  942. MODULE_DESCRIPTION("Compal Laptop Support");
  943. MODULE_VERSION(DRIVER_VERSION);
  944. MODULE_LICENSE("GPL");