1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2013 Capella Microsystems Inc. |
4 | * Author: Kevin Tsai <ktsai@capellamicro.com> |
5 | */ |
6 | |
7 | #include <linux/acpi.h> |
8 | #include <linux/delay.h> |
9 | #include <linux/err.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/mutex.h> |
12 | #include <linux/module.h> |
13 | #include <linux/mod_devicetable.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/regulator/consumer.h> |
16 | #include <linux/iio/iio.h> |
17 | #include <linux/iio/sysfs.h> |
18 | #include <linux/iio/events.h> |
19 | #include <linux/init.h> |
20 | |
21 | /* Registers Address */ |
22 | #define CM32181_REG_ADDR_CMD 0x00 |
23 | #define CM32181_REG_ADDR_WH 0x01 |
24 | #define CM32181_REG_ADDR_WL 0x02 |
25 | #define CM32181_REG_ADDR_TEST 0x03 |
26 | #define CM32181_REG_ADDR_ALS 0x04 |
27 | #define CM32181_REG_ADDR_STATUS 0x06 |
28 | #define CM32181_REG_ADDR_ID 0x07 |
29 | |
30 | /* Number of Configurable Registers */ |
31 | #define CM32181_CONF_REG_NUM 4 |
32 | |
33 | /* CMD register */ |
34 | #define CM32181_CMD_ALS_DISABLE BIT(0) |
35 | #define CM32181_CMD_ALS_INT_EN BIT(1) |
36 | #define CM32181_CMD_ALS_THRES_WINDOW BIT(2) |
37 | |
38 | #define CM32181_CMD_ALS_PERS_SHIFT 4 |
39 | #define CM32181_CMD_ALS_PERS_MASK (0x03 << CM32181_CMD_ALS_PERS_SHIFT) |
40 | #define CM32181_CMD_ALS_PERS_DEFAULT (0x01 << CM32181_CMD_ALS_PERS_SHIFT) |
41 | |
42 | #define CM32181_CMD_ALS_IT_SHIFT 6 |
43 | #define CM32181_CMD_ALS_IT_MASK (0x0F << CM32181_CMD_ALS_IT_SHIFT) |
44 | #define CM32181_CMD_ALS_IT_DEFAULT (0x00 << CM32181_CMD_ALS_IT_SHIFT) |
45 | |
46 | #define CM32181_CMD_ALS_SM_SHIFT 11 |
47 | #define CM32181_CMD_ALS_SM_MASK (0x03 << CM32181_CMD_ALS_SM_SHIFT) |
48 | #define CM32181_CMD_ALS_SM_DEFAULT (0x01 << CM32181_CMD_ALS_SM_SHIFT) |
49 | |
50 | #define CM32181_LUX_PER_BIT 500 /* ALS_SM=01 IT=800ms */ |
51 | #define CM32181_LUX_PER_BIT_RESOLUTION 100000 |
52 | #define CM32181_LUX_PER_BIT_BASE_IT 800000 /* Based on IT=800ms */ |
53 | #define CM32181_CALIBSCALE_DEFAULT 100000 |
54 | #define CM32181_CALIBSCALE_RESOLUTION 100000 |
55 | |
56 | #define SMBUS_ALERT_RESPONSE_ADDRESS 0x0c |
57 | |
58 | /* CPM0 Index 0: device-id (3218 or 32181), 1: Unknown, 2: init_regs_bitmap */ |
59 | #define CPM0_REGS_BITMAP 2 |
60 | #define 3 |
61 | |
62 | /* CPM1 Index 0: lux_per_bit, 1: calibscale, 2: resolution (100000) */ |
63 | #define CPM1_LUX_PER_BIT 0 |
64 | #define CPM1_CALIBSCALE 1 |
65 | #define CPM1_SIZE 3 |
66 | |
67 | /* CM3218 Family */ |
68 | static const int cm3218_als_it_bits[] = { 0, 1, 2, 3 }; |
69 | static const int cm3218_als_it_values[] = { 100000, 200000, 400000, 800000 }; |
70 | |
71 | /* CM32181 Family */ |
72 | static const int cm32181_als_it_bits[] = { 12, 8, 0, 1, 2, 3 }; |
73 | static const int cm32181_als_it_values[] = { |
74 | 25000, 50000, 100000, 200000, 400000, 800000 |
75 | }; |
76 | |
77 | struct cm32181_chip { |
78 | struct i2c_client *client; |
79 | struct device *dev; |
80 | struct mutex lock; |
81 | u16 conf_regs[CM32181_CONF_REG_NUM]; |
82 | unsigned long init_regs_bitmap; |
83 | int calibscale; |
84 | int lux_per_bit; |
85 | int lux_per_bit_base_it; |
86 | int num_als_it; |
87 | const int *als_it_bits; |
88 | const int *als_it_values; |
89 | }; |
90 | |
91 | static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2); |
92 | |
93 | #ifdef CONFIG_ACPI |
94 | /** |
95 | * cm32181_acpi_get_cpm() - Get CPM object from ACPI |
96 | * @dev: pointer of struct device. |
97 | * @obj_name: pointer of ACPI object name. |
98 | * @values: pointer of array for return elements. |
99 | * @count: maximum size of return array. |
100 | * |
101 | * Convert ACPI CPM table to array. |
102 | * |
103 | * Return: -ENODEV for fail. Otherwise is number of elements. |
104 | */ |
105 | static int cm32181_acpi_get_cpm(struct device *dev, char *obj_name, |
106 | u64 *values, int count) |
107 | { |
108 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; |
109 | union acpi_object *cpm, *elem; |
110 | acpi_handle handle; |
111 | acpi_status status; |
112 | int i; |
113 | |
114 | handle = ACPI_HANDLE(dev); |
115 | if (!handle) |
116 | return -ENODEV; |
117 | |
118 | status = acpi_evaluate_object(object: handle, pathname: obj_name, NULL, return_object_buffer: &buffer); |
119 | if (ACPI_FAILURE(status)) { |
120 | dev_err(dev, "object %s not found\n" , obj_name); |
121 | return -ENODEV; |
122 | } |
123 | |
124 | cpm = buffer.pointer; |
125 | if (cpm->package.count > count) |
126 | dev_warn(dev, "%s table contains %u values, only using first %d values\n" , |
127 | obj_name, cpm->package.count, count); |
128 | |
129 | count = min_t(int, cpm->package.count, count); |
130 | for (i = 0; i < count; i++) { |
131 | elem = &(cpm->package.elements[i]); |
132 | values[i] = elem->integer.value; |
133 | } |
134 | |
135 | kfree(objp: buffer.pointer); |
136 | |
137 | return count; |
138 | } |
139 | |
140 | static void cm32181_acpi_parse_cpm_tables(struct cm32181_chip *cm32181) |
141 | { |
142 | u64 vals[CPM0_HEADER_SIZE + CM32181_CONF_REG_NUM]; |
143 | struct device *dev = cm32181->dev; |
144 | int i, count; |
145 | |
146 | count = cm32181_acpi_get_cpm(dev, obj_name: "CPM0" , values: vals, ARRAY_SIZE(vals)); |
147 | if (count <= CPM0_HEADER_SIZE) |
148 | return; |
149 | |
150 | count -= CPM0_HEADER_SIZE; |
151 | |
152 | cm32181->init_regs_bitmap = vals[CPM0_REGS_BITMAP]; |
153 | cm32181->init_regs_bitmap &= GENMASK(count - 1, 0); |
154 | for_each_set_bit(i, &cm32181->init_regs_bitmap, count) |
155 | cm32181->conf_regs[i] = vals[CPM0_HEADER_SIZE + i]; |
156 | |
157 | count = cm32181_acpi_get_cpm(dev, obj_name: "CPM1" , values: vals, ARRAY_SIZE(vals)); |
158 | if (count != CPM1_SIZE) |
159 | return; |
160 | |
161 | cm32181->lux_per_bit = vals[CPM1_LUX_PER_BIT]; |
162 | |
163 | /* Check for uncalibrated devices */ |
164 | if (vals[CPM1_CALIBSCALE] == CM32181_CALIBSCALE_DEFAULT) |
165 | return; |
166 | |
167 | cm32181->calibscale = vals[CPM1_CALIBSCALE]; |
168 | /* CPM1 lux_per_bit is for the current it value */ |
169 | cm32181_read_als_it(cm32181, val2: &cm32181->lux_per_bit_base_it); |
170 | } |
171 | #else |
172 | static void cm32181_acpi_parse_cpm_tables(struct cm32181_chip *cm32181) |
173 | { |
174 | } |
175 | #endif /* CONFIG_ACPI */ |
176 | |
177 | /** |
178 | * cm32181_reg_init() - Initialize CM32181 registers |
179 | * @cm32181: pointer of struct cm32181. |
180 | * |
181 | * Initialize CM32181 ambient light sensor register to default values. |
182 | * |
183 | * Return: 0 for success; otherwise for error code. |
184 | */ |
185 | static int cm32181_reg_init(struct cm32181_chip *cm32181) |
186 | { |
187 | struct i2c_client *client = cm32181->client; |
188 | int i; |
189 | s32 ret; |
190 | |
191 | ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ID); |
192 | if (ret < 0) |
193 | return ret; |
194 | |
195 | /* check device ID */ |
196 | switch (ret & 0xFF) { |
197 | case 0x18: /* CM3218 */ |
198 | cm32181->num_als_it = ARRAY_SIZE(cm3218_als_it_bits); |
199 | cm32181->als_it_bits = cm3218_als_it_bits; |
200 | cm32181->als_it_values = cm3218_als_it_values; |
201 | break; |
202 | case 0x81: /* CM32181 */ |
203 | case 0x82: /* CM32182, fully compat. with CM32181 */ |
204 | cm32181->num_als_it = ARRAY_SIZE(cm32181_als_it_bits); |
205 | cm32181->als_it_bits = cm32181_als_it_bits; |
206 | cm32181->als_it_values = cm32181_als_it_values; |
207 | break; |
208 | default: |
209 | return -ENODEV; |
210 | } |
211 | |
212 | /* Default Values */ |
213 | cm32181->conf_regs[CM32181_REG_ADDR_CMD] = |
214 | CM32181_CMD_ALS_IT_DEFAULT | CM32181_CMD_ALS_SM_DEFAULT; |
215 | cm32181->init_regs_bitmap = BIT(CM32181_REG_ADDR_CMD); |
216 | cm32181->calibscale = CM32181_CALIBSCALE_DEFAULT; |
217 | cm32181->lux_per_bit = CM32181_LUX_PER_BIT; |
218 | cm32181->lux_per_bit_base_it = CM32181_LUX_PER_BIT_BASE_IT; |
219 | |
220 | if (ACPI_HANDLE(cm32181->dev)) |
221 | cm32181_acpi_parse_cpm_tables(cm32181); |
222 | |
223 | /* Initialize registers*/ |
224 | for_each_set_bit(i, &cm32181->init_regs_bitmap, CM32181_CONF_REG_NUM) { |
225 | ret = i2c_smbus_write_word_data(client, command: i, |
226 | value: cm32181->conf_regs[i]); |
227 | if (ret < 0) |
228 | return ret; |
229 | } |
230 | |
231 | return 0; |
232 | } |
233 | |
234 | /** |
235 | * cm32181_read_als_it() - Get sensor integration time (ms) |
236 | * @cm32181: pointer of struct cm32181 |
237 | * @val2: pointer of int to load the als_it value. |
238 | * |
239 | * Report the current integration time in milliseconds. |
240 | * |
241 | * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. |
242 | */ |
243 | static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2) |
244 | { |
245 | u16 als_it; |
246 | int i; |
247 | |
248 | als_it = cm32181->conf_regs[CM32181_REG_ADDR_CMD]; |
249 | als_it &= CM32181_CMD_ALS_IT_MASK; |
250 | als_it >>= CM32181_CMD_ALS_IT_SHIFT; |
251 | for (i = 0; i < cm32181->num_als_it; i++) { |
252 | if (als_it == cm32181->als_it_bits[i]) { |
253 | *val2 = cm32181->als_it_values[i]; |
254 | return IIO_VAL_INT_PLUS_MICRO; |
255 | } |
256 | } |
257 | |
258 | return -EINVAL; |
259 | } |
260 | |
261 | /** |
262 | * cm32181_write_als_it() - Write sensor integration time |
263 | * @cm32181: pointer of struct cm32181. |
264 | * @val: integration time by millisecond. |
265 | * |
266 | * Convert integration time (ms) to sensor value. |
267 | * |
268 | * Return: i2c_smbus_write_word_data command return value. |
269 | */ |
270 | static int cm32181_write_als_it(struct cm32181_chip *cm32181, int val) |
271 | { |
272 | struct i2c_client *client = cm32181->client; |
273 | u16 als_it; |
274 | int ret, i, n; |
275 | |
276 | n = cm32181->num_als_it; |
277 | for (i = 0; i < n; i++) |
278 | if (val <= cm32181->als_it_values[i]) |
279 | break; |
280 | if (i >= n) |
281 | i = n - 1; |
282 | |
283 | als_it = cm32181->als_it_bits[i]; |
284 | als_it <<= CM32181_CMD_ALS_IT_SHIFT; |
285 | |
286 | mutex_lock(&cm32181->lock); |
287 | cm32181->conf_regs[CM32181_REG_ADDR_CMD] &= |
288 | ~CM32181_CMD_ALS_IT_MASK; |
289 | cm32181->conf_regs[CM32181_REG_ADDR_CMD] |= |
290 | als_it; |
291 | ret = i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, |
292 | value: cm32181->conf_regs[CM32181_REG_ADDR_CMD]); |
293 | mutex_unlock(lock: &cm32181->lock); |
294 | |
295 | return ret; |
296 | } |
297 | |
298 | /** |
299 | * cm32181_get_lux() - report current lux value |
300 | * @cm32181: pointer of struct cm32181. |
301 | * |
302 | * Convert sensor raw data to lux. It depends on integration |
303 | * time and calibscale variable. |
304 | * |
305 | * Return: Positive value is lux, otherwise is error code. |
306 | */ |
307 | static int cm32181_get_lux(struct cm32181_chip *cm32181) |
308 | { |
309 | struct i2c_client *client = cm32181->client; |
310 | int ret; |
311 | int als_it; |
312 | u64 lux; |
313 | |
314 | ret = cm32181_read_als_it(cm32181, val2: &als_it); |
315 | if (ret < 0) |
316 | return -EINVAL; |
317 | |
318 | lux = cm32181->lux_per_bit; |
319 | lux *= cm32181->lux_per_bit_base_it; |
320 | lux = div_u64(dividend: lux, divisor: als_it); |
321 | |
322 | ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ALS); |
323 | if (ret < 0) |
324 | return ret; |
325 | |
326 | lux *= ret; |
327 | lux *= cm32181->calibscale; |
328 | lux = div_u64(dividend: lux, CM32181_CALIBSCALE_RESOLUTION); |
329 | lux = div_u64(dividend: lux, CM32181_LUX_PER_BIT_RESOLUTION); |
330 | |
331 | if (lux > 0xFFFF) |
332 | lux = 0xFFFF; |
333 | |
334 | return lux; |
335 | } |
336 | |
337 | static int cm32181_read_raw(struct iio_dev *indio_dev, |
338 | struct iio_chan_spec const *chan, |
339 | int *val, int *val2, long mask) |
340 | { |
341 | struct cm32181_chip *cm32181 = iio_priv(indio_dev); |
342 | int ret; |
343 | |
344 | switch (mask) { |
345 | case IIO_CHAN_INFO_PROCESSED: |
346 | ret = cm32181_get_lux(cm32181); |
347 | if (ret < 0) |
348 | return ret; |
349 | *val = ret; |
350 | return IIO_VAL_INT; |
351 | case IIO_CHAN_INFO_CALIBSCALE: |
352 | *val = cm32181->calibscale; |
353 | return IIO_VAL_INT; |
354 | case IIO_CHAN_INFO_INT_TIME: |
355 | *val = 0; |
356 | ret = cm32181_read_als_it(cm32181, val2); |
357 | return ret; |
358 | } |
359 | |
360 | return -EINVAL; |
361 | } |
362 | |
363 | static int cm32181_write_raw(struct iio_dev *indio_dev, |
364 | struct iio_chan_spec const *chan, |
365 | int val, int val2, long mask) |
366 | { |
367 | struct cm32181_chip *cm32181 = iio_priv(indio_dev); |
368 | int ret; |
369 | |
370 | switch (mask) { |
371 | case IIO_CHAN_INFO_CALIBSCALE: |
372 | cm32181->calibscale = val; |
373 | return val; |
374 | case IIO_CHAN_INFO_INT_TIME: |
375 | ret = cm32181_write_als_it(cm32181, val: val2); |
376 | return ret; |
377 | } |
378 | |
379 | return -EINVAL; |
380 | } |
381 | |
382 | /** |
383 | * cm32181_get_it_available() - Get available ALS IT value |
384 | * @dev: pointer of struct device. |
385 | * @attr: pointer of struct device_attribute. |
386 | * @buf: pointer of return string buffer. |
387 | * |
388 | * Display the available integration time values by millisecond. |
389 | * |
390 | * Return: string length. |
391 | */ |
392 | static ssize_t cm32181_get_it_available(struct device *dev, |
393 | struct device_attribute *attr, char *buf) |
394 | { |
395 | struct cm32181_chip *cm32181 = iio_priv(indio_dev: dev_to_iio_dev(dev)); |
396 | int i, n, len; |
397 | |
398 | n = cm32181->num_als_it; |
399 | for (i = 0, len = 0; i < n; i++) |
400 | len += sprintf(buf: buf + len, fmt: "0.%06u " , cm32181->als_it_values[i]); |
401 | return len + sprintf(buf: buf + len, fmt: "\n" ); |
402 | } |
403 | |
404 | static const struct iio_chan_spec cm32181_channels[] = { |
405 | { |
406 | .type = IIO_LIGHT, |
407 | .info_mask_separate = |
408 | BIT(IIO_CHAN_INFO_PROCESSED) | |
409 | BIT(IIO_CHAN_INFO_CALIBSCALE) | |
410 | BIT(IIO_CHAN_INFO_INT_TIME), |
411 | } |
412 | }; |
413 | |
414 | static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, |
415 | S_IRUGO, cm32181_get_it_available, NULL, 0); |
416 | |
417 | static struct attribute *cm32181_attributes[] = { |
418 | &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, |
419 | NULL, |
420 | }; |
421 | |
422 | static const struct attribute_group cm32181_attribute_group = { |
423 | .attrs = cm32181_attributes |
424 | }; |
425 | |
426 | static const struct iio_info cm32181_info = { |
427 | .read_raw = &cm32181_read_raw, |
428 | .write_raw = &cm32181_write_raw, |
429 | .attrs = &cm32181_attribute_group, |
430 | }; |
431 | |
432 | static void cm32181_unregister_dummy_client(void *data) |
433 | { |
434 | struct i2c_client *client = data; |
435 | |
436 | /* Unregister the dummy client */ |
437 | i2c_unregister_device(client); |
438 | } |
439 | |
440 | static int cm32181_probe(struct i2c_client *client) |
441 | { |
442 | struct device *dev = &client->dev; |
443 | struct cm32181_chip *cm32181; |
444 | struct iio_dev *indio_dev; |
445 | int ret; |
446 | |
447 | indio_dev = devm_iio_device_alloc(parent: dev, sizeof_priv: sizeof(*cm32181)); |
448 | if (!indio_dev) |
449 | return -ENOMEM; |
450 | |
451 | i2c_set_clientdata(client, data: indio_dev); |
452 | |
453 | /* |
454 | * Some ACPI systems list 2 I2C resources for the CM3218 sensor, the |
455 | * SMBus Alert Response Address (ARA, 0x0c) and the actual I2C address. |
456 | * Detect this and take the following step to deal with it: |
457 | * 1. When a SMBus Alert capable sensor has an Alert asserted, it will |
458 | * not respond on its actual I2C address. Read a byte from the ARA |
459 | * to clear any pending Alerts. |
460 | * 2. Create a "dummy" client for the actual I2C address and |
461 | * use that client to communicate with the sensor. |
462 | */ |
463 | if (ACPI_HANDLE(dev) && client->addr == SMBUS_ALERT_RESPONSE_ADDRESS) { |
464 | struct i2c_board_info board_info = { .type = "dummy" }; |
465 | |
466 | i2c_smbus_read_byte(client); |
467 | |
468 | client = i2c_acpi_new_device(dev, index: 1, info: &board_info); |
469 | if (IS_ERR(ptr: client)) |
470 | return PTR_ERR(ptr: client); |
471 | |
472 | ret = devm_add_action_or_reset(dev, cm32181_unregister_dummy_client, client); |
473 | if (ret) |
474 | return ret; |
475 | } |
476 | |
477 | cm32181 = iio_priv(indio_dev); |
478 | cm32181->client = client; |
479 | cm32181->dev = dev; |
480 | |
481 | mutex_init(&cm32181->lock); |
482 | indio_dev->channels = cm32181_channels; |
483 | indio_dev->num_channels = ARRAY_SIZE(cm32181_channels); |
484 | indio_dev->info = &cm32181_info; |
485 | indio_dev->name = dev_name(dev); |
486 | indio_dev->modes = INDIO_DIRECT_MODE; |
487 | |
488 | ret = cm32181_reg_init(cm32181); |
489 | if (ret) { |
490 | dev_err(dev, "%s: register init failed\n" , __func__); |
491 | return ret; |
492 | } |
493 | |
494 | ret = devm_iio_device_register(dev, indio_dev); |
495 | if (ret) { |
496 | dev_err(dev, "%s: regist device failed\n" , __func__); |
497 | return ret; |
498 | } |
499 | |
500 | return 0; |
501 | } |
502 | |
503 | static int cm32181_suspend(struct device *dev) |
504 | { |
505 | struct cm32181_chip *cm32181 = iio_priv(indio_dev: dev_get_drvdata(dev)); |
506 | struct i2c_client *client = cm32181->client; |
507 | |
508 | return i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, |
509 | CM32181_CMD_ALS_DISABLE); |
510 | } |
511 | |
512 | static int cm32181_resume(struct device *dev) |
513 | { |
514 | struct cm32181_chip *cm32181 = iio_priv(indio_dev: dev_get_drvdata(dev)); |
515 | struct i2c_client *client = cm32181->client; |
516 | |
517 | return i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, |
518 | value: cm32181->conf_regs[CM32181_REG_ADDR_CMD]); |
519 | } |
520 | |
521 | static DEFINE_SIMPLE_DEV_PM_OPS(cm32181_pm_ops, cm32181_suspend, cm32181_resume); |
522 | |
523 | static const struct of_device_id cm32181_of_match[] = { |
524 | { .compatible = "capella,cm3218" }, |
525 | { .compatible = "capella,cm32181" }, |
526 | { } |
527 | }; |
528 | MODULE_DEVICE_TABLE(of, cm32181_of_match); |
529 | |
530 | #ifdef CONFIG_ACPI |
531 | static const struct acpi_device_id cm32181_acpi_match[] = { |
532 | { "CPLM3218" , 0 }, |
533 | { } |
534 | }; |
535 | MODULE_DEVICE_TABLE(acpi, cm32181_acpi_match); |
536 | #endif |
537 | |
538 | static struct i2c_driver cm32181_driver = { |
539 | .driver = { |
540 | .name = "cm32181" , |
541 | .acpi_match_table = ACPI_PTR(cm32181_acpi_match), |
542 | .of_match_table = cm32181_of_match, |
543 | .pm = pm_sleep_ptr(&cm32181_pm_ops), |
544 | }, |
545 | .probe = cm32181_probe, |
546 | }; |
547 | |
548 | module_i2c_driver(cm32181_driver); |
549 | |
550 | MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>" ); |
551 | MODULE_DESCRIPTION("CM32181 ambient light sensor driver" ); |
552 | MODULE_LICENSE("GPL" ); |
553 | |