From patchwork Sat Dec 9 23:25:06 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 121303 Delivered-To: patch@linaro.org Received: by 10.140.22.227 with SMTP id 90csp1167646qgn; Sat, 9 Dec 2017 15:25:56 -0800 (PST) X-Google-Smtp-Source: AGs4zMaObb2dyiSRRqVnyr3SYfysjCAudlZ4fph1h9ix8Yt6Tk3WUj5/WQyS7K74sUDcmKDsub8/ X-Received: by 10.99.126.11 with SMTP id z11mr34351733pgc.281.1512861956649; Sat, 09 Dec 2017 15:25:56 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1512861956; cv=none; d=google.com; s=arc-20160816; b=H4TXL4jfzpk7tBMSVLIdIpW3DSge1QaED3avCyhN1a2DqHmm8aGEmf+PbQrlUa49eu 77j5xG1/OdXQ1DyxIAnqolGac6TDOynGwilF9PPCBwhx1nqG+3nn14pO8xa6l2Ggh58c UrkKE2S47IG75hGE3ZSlxlBIM5QSKlvg7SR5+j9LTUBMD0gnOB86IFd6Z00cs52r+UoA sLZ14xJ+rHTdzC5ylCviUj3p+2UDFY1o3T6B01T15to9BSxdbxFHQIGkjpe+NkCRBwvn e3cGTB8CjJ2UiVePhQoCSFe4aOZ6Z9Gs7sEP9IjRRkaik5G6X2OhY6wTeb2pmVKiE+mR 4Bcg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :arc-authentication-results; bh=YLdVnsdUAsY8XRC14Fzz1FNprmq8e8V8Cv/2mLnbz0E=; b=eryFBg6CgP4q4CEQvlwDh7WZWoLpK3o4wMijaIPv/Hrf3oPG63EpT4+5s2zGldn+cC B7zImF8vr+GQuU4uS8tq4tEs39XcipWlQXvy1DMIUPL6Ma1xwGf8+ffKpEw7OcUQSs6i UXXekl1vPhLMSBvKwMi7FEu35OVouD/d1nJNpGsuDOXUp8n0ZJC+3Fh2HCQmkdFzgvSf Kk/4ThlUIj/Tc9C06iJgH0hbbwkZp8MPLAEaSml5gBNTFcgiLNiQuwSZCpnhgDyJzc2R ZJPvPoY4kgKjpF7F/3pGGrUuCm+jv71B2ecxmWcSJ6NDBPwxBLk2TKekfXbZmaLMyIUk dt/Q== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id k125si7735059pgk.495.2017.12.09.15.25.56; Sat, 09 Dec 2017 15:25:56 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751444AbdLIXZo (ORCPT + 20 others); Sat, 9 Dec 2017 18:25:44 -0500 Received: from mx2.suse.de ([195.135.220.15]:49257 "EHLO mx2.suse.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751190AbdLIXZl (ORCPT ); Sat, 9 Dec 2017 18:25:41 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.220.254]) by mx2.suse.de (Postfix) with ESMTP id 9FFC0ACA1; Sat, 9 Dec 2017 23:25:39 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-iio@vger.kernel.org Cc: linux-kernel@vger.kernel.org, =?utf-8?q?Andreas_F=C3=A4rber?= , Marius Tarcatu , Rob Herring , Mark Rutland , devicetree@vger.kernel.org (open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS) Subject: [PATCH 1/2] dt-bindings: Add Infineon TLV493D-A1B6 Date: Sun, 10 Dec 2017 00:25:06 +0100 Message-Id: <20171209232507.18594-2-afaerber@suse.de> X-Mailer: git-send-email 2.13.6 In-Reply-To: <20171209232507.18594-1-afaerber@suse.de> References: <20171209232507.18594-1-afaerber@suse.de> MIME-Version: 1.0 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org The Infineon TLV493D-A1B6 is an I2C-based 3D Magnetic Sensor. Cc: Marius Tarcatu Signed-off-by: Andreas Färber --- Documentation/devicetree/bindings/trivial-devices.txt | 1 + 1 file changed, 1 insertion(+) -- 2.13.6 diff --git a/Documentation/devicetree/bindings/trivial-devices.txt b/Documentation/devicetree/bindings/trivial-devices.txt index 5f3143f97098..7ad1939cb0d6 100644 --- a/Documentation/devicetree/bindings/trivial-devices.txt +++ b/Documentation/devicetree/bindings/trivial-devices.txt @@ -63,6 +63,7 @@ fsl,sgtl5000 SGTL5000: Ultra Low-Power Audio Codec gmt,g751 G751: Digital Temperature Sensor and Thermal Watchdog with Two-Wire Interface infineon,slb9635tt Infineon SLB9635 (Soft-) I2C TPM (old protocol, max 100khz) infineon,slb9645tt Infineon SLB9645 I2C TPM (new protocol, max 400khz) +infineon,tlv493d-a1b6 Infineon TLV493D-A1B6 I2C 3D Magnetic Sensor isil,isl1208 Intersil ISL1208 Low Power RTC with Battery Backed SRAM isil,isl1218 Intersil ISL1218 Low Power RTC with Battery Backed SRAM isil,isl12022 Intersil ISL12022 Real-time Clock From patchwork Sat Dec 9 23:25:07 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 121304 Delivered-To: patch@linaro.org Received: by 10.140.22.227 with SMTP id 90csp1167649qgn; Sat, 9 Dec 2017 15:25:57 -0800 (PST) X-Google-Smtp-Source: AGs4zMajPr/qcSfL5QHWIGGs50aesk876cGlhsMN5e8LlQbibDYJac2IANkNGn3Vnq6AXoIF5tJs X-Received: by 10.84.248.145 with SMTP id q17mr33642358pll.416.1512861957076; Sat, 09 Dec 2017 15:25:57 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1512861957; cv=none; d=google.com; s=arc-20160816; b=ZJcTOeS/O36Fy/HH3m79gfZyPby4JsDimSiFaXoLbTXVNlWzS/JPZI2HYpFrMiyY+q mp8fm1Tg8DI6HPjlLATnDEfCaH7FOYheZDnNm5S1bCPt6Bibjv+VYqD3sTMujO6KcTds vaBh0zWbg4afMARwUAJUiPQMtyYj7XrTNmhzAqIz8c12rhkeT4a9pLWidjppgBJFAUK1 pPLJ6C2Z6jlz2RSrN/S6GOVzT256fAUI+i7zqAtXT3IRBrHPZlAVz2uGjbaymu0zFxef Q1nN47r2ME5cRqPWcbkFy0T6AqLgY6X9pQDPwVwbNjN4M+2zJS+nr5iCtnh9Yn1o0UnW Nuiw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :arc-authentication-results; bh=ukodkcYZeJP3ZiFGLL7YQipvGL7dbYTJGMhl3Ezeqro=; b=Xtb/YhspgwuqgNWohQraMKfLkc64IVefpStGjL6LnRiI3z/2eOcxh8QCwgQtzugC7/ VzHtTQnf+sfAUlH9B4bZoB/ldyn1haXrP32u0rtPV033am9VHzOUC7VsMGmQaK+iIVyY g9UINYQkxCzkgBSHNpzcQ3rzD/6BWRsixCJ+DHxDWXNpPtoitukQEPgZcfXfTBYp7QmX 7Q0yk9Oy46qRIwm/rfkQUGdZ5HiAJF/MbNW11pPiJzUINVgMosI/P9CSFwn4bKSp3Eg8 lYRWAF6JUyhYuo+1b6NXbBFt8mwuGLK/nrL9hwhB7Lkq4ds08s9bWOb/nY+rP4breB/a N1Zg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id k125si7735059pgk.495.2017.12.09.15.25.56; Sat, 09 Dec 2017 15:25:57 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751641AbdLIXZr (ORCPT + 20 others); Sat, 9 Dec 2017 18:25:47 -0500 Received: from mx2.suse.de ([195.135.220.15]:49266 "EHLO mx2.suse.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751263AbdLIXZn (ORCPT ); Sat, 9 Dec 2017 18:25:43 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay1.suse.de (charybdis-ext.suse.de [195.135.220.254]) by mx2.suse.de (Postfix) with ESMTP id 3AB72ADBF; Sat, 9 Dec 2017 23:25:41 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-iio@vger.kernel.org Cc: linux-kernel@vger.kernel.org, =?utf-8?q?Andreas_F=C3=A4rber?= , Marius Tarcatu , Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler Subject: [PATCH 2/2] iio: magnetometer: Add Infineon TLV493D-A1B6 Date: Sun, 10 Dec 2017 00:25:07 +0100 Message-Id: <20171209232507.18594-3-afaerber@suse.de> X-Mailer: git-send-email 2.13.6 In-Reply-To: <20171209232507.18594-1-afaerber@suse.de> References: <20171209232507.18594-1-afaerber@suse.de> MIME-Version: 1.0 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org The Infineon TLV493D is a 3D magnetic sensor, A1B6 is I2C based. It is found among others on the Infineon 3D Magnetic Sensor 2Go Kit, which allows to detach the sensor as a breakout board. Cc: Marius Tarcatu Signed-off-by: Andreas Färber --- drivers/iio/magnetometer/Kconfig | 9 + drivers/iio/magnetometer/Makefile | 2 + drivers/iio/magnetometer/tlv493d.c | 328 +++++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+) create mode 100644 drivers/iio/magnetometer/tlv493d.c -- 2.13.6 Acked-by: Philippe Ombredanne diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index ed9d776d01af..5945d88a1595 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -175,4 +175,13 @@ config SENSORS_HMC5843_SPI - hmc5843_core (core functions) - hmc5843_spi (support for HMC5983) +config TLV493D + tristate "Infineon TLV493D 3D Magnetic Sensor" + depends on I2C + help + Say Y here to build support for Infineon TLV493D-A1B6 I2C-based + 3-axis magnetometer. + + This driver can also be built as a module and will be called tlv493d. + endmenu diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile index 664b2f866472..df6ad23fee65 100644 --- a/drivers/iio/magnetometer/Makefile +++ b/drivers/iio/magnetometer/Makefile @@ -24,3 +24,5 @@ obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o + +obj-$(CONFIG_TLV493D) += tlv493d.o diff --git a/drivers/iio/magnetometer/tlv493d.c b/drivers/iio/magnetometer/tlv493d.c new file mode 100644 index 000000000000..d2fe296b2c80 --- /dev/null +++ b/drivers/iio/magnetometer/tlv493d.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Infineon TLV493D-A1B6 3D magnetic sensor + * + * Copyright (c) 2016-2017 Andreas Färber + */ + +#include +#include +#include +#include +#include +#include + +#define MOD1_LOW BIT(0) +#define MOD1_FAST BIT(1) +#define MOD1_INT BIT(2) +#define MOD1_P BIT(7) + +#define MOD2_PT BIT(5) + +struct tlv493d { + struct i2c_client *i2c; + struct iio_mount_matrix mount_matrix; + u8 factset1, factset2, factset3; +}; + +static int tlv493d_read_regs(struct tlv493d *s, u8 *buf, int len) +{ + int ret; + + ret = i2c_master_recv(s->i2c, buf, len); + if (ret < len) { + dev_err(&s->i2c->dev, "failed to read registers (%d)", ret); + return ret; + } + + return 0; +} + +static unsigned int tlv493d_parity(u32 v) +{ + v ^= v >> 16; + v ^= v >> 8; + v ^= v >> 4; + v &= 0xf; + return (0x6996 >> v) & 1; +} + +#define MOD1_RES_MASK (0x3 << 3) +#define MOD1_IICADDR_MASK (0x3 << 5) + +static int tlv493d_write_regs(struct tlv493d *s, const u8 *regs, int len) +{ + u8 buf[4]; + int ret; + + if (len != ARRAY_SIZE(buf)) + return -EINVAL; + + buf[0] = 0; + buf[1] = s->factset1 & (MOD1_IICADDR_MASK | MOD1_RES_MASK); + buf[1] |= regs[1] & ~(MOD1_P | MOD1_IICADDR_MASK | MOD1_RES_MASK); + buf[2] = s->factset2; + buf[3] = MOD2_PT | (s->factset3 & 0x1f); + buf[3] |= regs[3] & ~(MOD2_PT | 0x1f); + + if ((buf[3] & MOD2_PT) && + !tlv493d_parity((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3])) + buf[1] |= MOD1_P; + + ret = i2c_master_send(s->i2c, buf, 4); + if (ret < 4) { + dev_err(&s->i2c->dev, "failed to write registers (%d)", ret); + return ret; + } + + return 0; +} + +static int tlv493d_power_down(struct tlv493d *s) +{ + u8 buf[4]; + + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + return tlv493d_write_regs(s, buf, 4); +} + +static const struct iio_mount_matrix * +tlv493d_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct tlv493d *s = iio_priv(indio_dev); + + return &s->mount_matrix; +} + +static const struct iio_chan_spec_ext_info tlv493d_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, tlv493d_get_mount_matrix), + {} +}; + +#define TLV493D_AXIS_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .channel2 = IIO_MOD_##axis, \ + .modified = 1, \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = tlv493d_ext_info, \ + } + +#define TLV493D_TEMP_CHANNEL(index) \ + { \ + .type = IIO_TEMP, \ + .channel2 = IIO_MOD_TEMP_OBJECT, \ + .modified = 1, \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + } + +static const struct iio_chan_spec tlv493d_channels[] = { + TLV493D_AXIS_CHANNEL(X, 0), + TLV493D_AXIS_CHANNEL(Y, 1), + TLV493D_AXIS_CHANNEL(Z, 2), + TLV493D_TEMP_CHANNEL(3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const unsigned long tlv493d_scan_masks[] = { 0xf, 0 }; + +static int tlv493d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tlv493d *s = iio_priv(indio_dev); + u8 buf[7]; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + case IIO_CHAN_INFO_RAW: + do { + ret = tlv493d_read_regs(s, buf, 7); + if (ret) + return ret; + } while ((buf[3] & 0x3) || (buf[5] & BIT(4))); + + switch (chan->address) { + case 0: + *val = (((int)(s8)buf[0]) << 4) | (buf[4] >> 4); + break; + case 1: + *val = (((int)(s8)buf[1]) << 4) | (buf[4] & 0xf); + break; + case 2: + *val = (((int)(s8)buf[2]) << 4) | (buf[5] & 0xf); + break; + case 3: + *val = (((int)(s8)(buf[3] & 0xf0)) << 4) | buf[6]; + if (mask != IIO_CHAN_INFO_RAW) + *val -= 340; + break; + default: + return -EINVAL; + } + *val2 = 0; + if (mask == IIO_CHAN_INFO_RAW) + return IIO_VAL_INT; + + switch (chan->type) { + case IIO_MAGN: + *val2 = (*val * 1000000) * 98 / 1000; + *val = *val2 / 1000000; + *val2 %= 1000000; + break; + case IIO_TEMP: + *val2 = (*val * 1000000) * 11 / 10; + *val = *val2 / 1000000; + *val2 %= 1000000; + /* According to datasheet, LSB offset is for 25°C */ + *val += 25; + break; + default: + return -EINVAL; + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = -340; + *val2 = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + /* 0.098 mT/LSB */ + *val = 0; + *val2 = 98000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + /* 1.1 °C/LSB */ + *val = 1; + *val2 = 100000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info tlv493d_iio_info = { + .read_raw = tlv493d_read_raw, +}; + +static int tlv493d_probe(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev; + struct tlv493d *tlv493d; + u8 buf[10]; + int ret; + + indio_dev = devm_iio_device_alloc(&i2c->dev, sizeof(*tlv493d)); + if (!indio_dev) + return -ENOMEM; + + tlv493d = iio_priv(indio_dev); + i2c_set_clientdata(i2c, indio_dev); + tlv493d->i2c = i2c; + + ret = of_iio_read_mount_matrix(&i2c->dev, "mount-matrix", + &tlv493d->mount_matrix); + if (ret) + return ret; + + indio_dev->dev.parent = &i2c->dev; + indio_dev->channels = tlv493d_channels; + indio_dev->num_channels = ARRAY_SIZE(tlv493d_channels); + indio_dev->info = &tlv493d_iio_info; + indio_dev->available_scan_masks = tlv493d_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = "tlv493d"; + + ret = tlv493d_read_regs(tlv493d, buf, 10); + if (ret) + return ret; + + tlv493d->factset1 = buf[7]; + tlv493d->factset2 = buf[8]; + tlv493d->factset3 = buf[9]; + + buf[0] = 0; + buf[1] = MOD1_FAST | MOD1_LOW; + buf[2] = 0; + buf[3] = 0; + ret = tlv493d_write_regs(tlv493d, buf, 4); + if (ret) + return ret; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&i2c->dev, "device registration failed"); + tlv493d_power_down(tlv493d); + return ret; + } + + return 0; +} + +static int tlv493d_remove(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(i2c); + struct tlv493d *tlv493d = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + tlv493d_power_down(tlv493d); + + return 0; +} + +static const struct of_device_id tlv493d_dt_ids[] = { + { .compatible = "infineon,tlv493d-a1b6" }, + {} +}; +MODULE_DEVICE_TABLE(of, tlv493d_dt_ids); + +static struct i2c_driver tlv493d_i2c_driver = { + .driver = { + .name = "tlv493d", + .of_match_table = tlv493d_dt_ids, + }, + .probe_new = tlv493d_probe, + .remove = tlv493d_remove, +}; + +module_i2c_driver(tlv493d_i2c_driver); + +MODULE_DESCRIPTION("TLV493D I2C driver"); +MODULE_AUTHOR("Andreas Faerber"); +MODULE_LICENSE("GPL");