0
0
mirror of https://git.openwrt.org/openwrt/openwrt.git synced 2024-11-22 04:56:15 +00:00
openwrt/target/linux/bcm27xx/patches-6.6/950-0345-drm-panel-Add-panel-driver-for-Ilitek-ILI9806E-panel.patch
Álvaro Fernández Rojas 8c405cdccc bcm27xx: add 6.6 kernel patches
The patches were generated from the RPi repo with the following command:
git format-patch v6.6.34..rpi-6.1.y

Some patches needed rebasing and, as usual, the applied and reverted, wireless
drivers, Github workflows, READMEs and defconfigs patches were removed.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-06-18 18:52:49 +02:00

537 lines
15 KiB
Diff

From 7a5df2564ca1e83585326c183dedd9db662aa389 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Wed, 5 Jan 2022 19:14:48 +0000
Subject: [PATCH 0345/1085] drm/panel: Add panel driver for Ilitek ILI9806E
panel
The Ilitek ILI9806E driver is used in the Pimoroni HyperPixel4
and potentially other displays. Whilst it can support multiple
interfaces, this driver only accounts for SPI configuration and
DPI video data.
Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
---
drivers/gpu/drm/panel/Kconfig | 11 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-ilitek-ili9806e.c | 484 ++++++++++++++++++
3 files changed, 496 insertions(+)
create mode 100644 drivers/gpu/drm/panel/panel-ilitek-ili9806e.c
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -194,6 +194,17 @@ config DRM_PANEL_ILITEK_ILI9341
QVGA (240x320) RGB panels. support serial & parallel rgb
interface.
+config DRM_PANEL_ILITEK_ILI9806E
+ tristate "Ilitek ILI9806E-based panels"
+ depends on OF && SPI
+ select DRM_KMS_HELPER
+ depends on DRM_GEM_CMA_HELPER
+ depends on BACKLIGHT_CLASS_DEVICE
+ select DRM_MIPI_DBI
+ help
+ Say Y if you want to enable support for panels based on the
+ Ilitek ILI9806e controller.
+
config DRM_PANEL_ILITEK_ILI9881C
tristate "Ilitek ILI9881C-based panels"
depends on OF
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI
obj-$(CONFIG_DRM_PANEL_HIMAX_HX8394) += panel-himax-hx8394.o
obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
+obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E) += panel-ilitek-ili9806e.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o
obj-$(CONFIG_DRM_PANEL_INNOLUX_EJ030NA) += panel-innolux-ej030na.o
obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ilitek-ili9806e.c
@@ -0,0 +1,484 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ilitek ILI9806E TFT LCD drm_panel driver.
+ *
+ * Copyright (C) 2022 Raspberry Pi Ltd
+ *
+ * Derived from drivers/drm/gpu/panel/panel-sitronix-st7789v.c
+ * Copyright (C) 2017 Free Electrons
+ */
+
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+struct ili9806 {
+ struct drm_panel panel;
+ struct spi_device *spi;
+ struct gpio_desc *reset;
+ struct regulator *power;
+ u32 bus_format;
+};
+
+#define ILI9806_DATA BIT(8)
+
+#define ILI9806_MAX_MSG_LEN 6
+
+struct ili9806e_msg {
+ unsigned int len;
+ u16 msg[ILI9806_MAX_MSG_LEN];
+};
+
+#define ILI9806_SET_PAGE(page) \
+ { \
+ .len = 6, \
+ .msg = { \
+ 0xFF, \
+ ILI9806_DATA | 0xFF, \
+ ILI9806_DATA | 0x98, \
+ ILI9806_DATA | 0x06, \
+ ILI9806_DATA | 0x04, \
+ ILI9806_DATA | (page) \
+ }, \
+ }
+
+#define ILI9806_SET_REG_PARAM(reg, data) \
+ { \
+ .len = 2, \
+ .msg = { \
+ (reg), \
+ ILI9806_DATA | (data), \
+ }, \
+ }
+
+#define ILI9806_SET_REG(reg) \
+ { \
+ .len = 1, \
+ .msg = { (reg) }, \
+ }
+
+static const struct ili9806e_msg panel_init[] = {
+ ILI9806_SET_PAGE(1),
+
+ /* interface mode
+ * SEPT_SDIO = 0 (spi interface transfer through SDA pin)
+ * SDO_STATUS = 1 (always output, but without output tri-state)
+ */
+ ILI9806_SET_REG_PARAM(0x08, 0x10),
+ /* display control
+ * VSPL = 1 (vertical sync polarity)
+ * HSPL = 0 (horizontal sync polarity)
+ * DPL = 0 (PCLK polarity)
+ * EPL = 1 (data enable polarity)
+ */
+ ILI9806_SET_REG_PARAM(0x21, 0x0d),
+ /* resolution control (0x02 = 480x800) */
+ ILI9806_SET_REG_PARAM(0x30, 0x02),
+ /* display inversion control (0x00 = column inversion) */
+ ILI9806_SET_REG_PARAM(0x31, 0x00),
+ /* power control
+ * EXB1T = 0 (internal charge pump)
+ * EXT_CPCK_SEL = 1 (pump clock control signal = output 2 x waveform)
+ * BT = 0 (DDVDH / DDVDL voltage = VCI x 2 / VCI x -2)
+ */
+ ILI9806_SET_REG_PARAM(0x40, 0x10),
+ /* power control
+ * DDVDH_CLP = 5.6 (DDVDH clamp leve)
+ * DDVDL_CLP = -5.6 (DDVDL clamp leve)
+ */
+ ILI9806_SET_REG_PARAM(0x41, 0x55),
+ /* power control
+ * VGH_CP = 2DDVDH - DDVDL (step up factor for VGH)
+ * VGL_CP = DDVDL + VCL - VCIP (step up factor for VGL)
+ */
+ ILI9806_SET_REG_PARAM(0x42, 0x02),
+ /* power control
+ * VGH_CLPEN = 0 (disable VGH clamp level)
+ * VGH_CLP = 9 (15.0 VGH clamp level - but this is disabled so not used?)
+ */
+ ILI9806_SET_REG_PARAM(0x43, 0x84),
+ /* power control
+ * VGL_CLPEN = 0 (disable VGL clamp level)
+ * VGL_CLP = 9 (-11.0 VGL clamp level - but this is disabled so not used?)
+ */
+ ILI9806_SET_REG_PARAM(0x44, 0x84),
+
+ /* power control
+ * VREG1OUT voltage for positive gamma?
+ */
+ ILI9806_SET_REG_PARAM(0x50, 0x78),
+ /* power control
+ * VREG2OUT voltage for negative gamma?
+ */
+ ILI9806_SET_REG_PARAM(0x51, 0x78),
+
+ ILI9806_SET_REG_PARAM(0x52, 0x00),
+ ILI9806_SET_REG_PARAM(0x53, 0x77),
+ ILI9806_SET_REG_PARAM(0x57, 0x60),
+ ILI9806_SET_REG_PARAM(0x60, 0x07),
+ ILI9806_SET_REG_PARAM(0x61, 0x00),
+ ILI9806_SET_REG_PARAM(0x62, 0x08),
+ ILI9806_SET_REG_PARAM(0x63, 0x00),
+ ILI9806_SET_REG_PARAM(0xA0, 0x00),
+ ILI9806_SET_REG_PARAM(0xA1, 0x07),
+ ILI9806_SET_REG_PARAM(0xA2, 0x0C),
+ ILI9806_SET_REG_PARAM(0xA3, 0x0B),
+ ILI9806_SET_REG_PARAM(0xA4, 0x03),
+ ILI9806_SET_REG_PARAM(0xA5, 0x07),
+ ILI9806_SET_REG_PARAM(0xA6, 0x06),
+ ILI9806_SET_REG_PARAM(0xA7, 0x04),
+ ILI9806_SET_REG_PARAM(0xA8, 0x08),
+ ILI9806_SET_REG_PARAM(0xA9, 0x0C),
+ ILI9806_SET_REG_PARAM(0xAA, 0x13),
+ ILI9806_SET_REG_PARAM(0xAB, 0x06),
+ ILI9806_SET_REG_PARAM(0xAC, 0x0D),
+ ILI9806_SET_REG_PARAM(0xAD, 0x19),
+ ILI9806_SET_REG_PARAM(0xAE, 0x10),
+ ILI9806_SET_REG_PARAM(0xAF, 0x00),
+ /* negative gamma control
+ * set the gray scale voltage to adjust the gamma characteristics of the panel
+ */
+ ILI9806_SET_REG_PARAM(0xC0, 0x00),
+ ILI9806_SET_REG_PARAM(0xC1, 0x07),
+ ILI9806_SET_REG_PARAM(0xC2, 0x0C),
+ ILI9806_SET_REG_PARAM(0xC3, 0x0B),
+ ILI9806_SET_REG_PARAM(0xC4, 0x03),
+ ILI9806_SET_REG_PARAM(0xC5, 0x07),
+ ILI9806_SET_REG_PARAM(0xC6, 0x07),
+ ILI9806_SET_REG_PARAM(0xC7, 0x04),
+ ILI9806_SET_REG_PARAM(0xC8, 0x08),
+ ILI9806_SET_REG_PARAM(0xC9, 0x0C),
+ ILI9806_SET_REG_PARAM(0xCA, 0x13),
+ ILI9806_SET_REG_PARAM(0xCB, 0x06),
+ ILI9806_SET_REG_PARAM(0xCC, 0x0D),
+ ILI9806_SET_REG_PARAM(0xCD, 0x18),
+ ILI9806_SET_REG_PARAM(0xCE, 0x10),
+ ILI9806_SET_REG_PARAM(0xCF, 0x00),
+
+ ILI9806_SET_PAGE(6),
+
+ ILI9806_SET_REG_PARAM(0x00, 0x20),
+ ILI9806_SET_REG_PARAM(0x01, 0x0A),
+ ILI9806_SET_REG_PARAM(0x02, 0x00),
+ ILI9806_SET_REG_PARAM(0x03, 0x00),
+ ILI9806_SET_REG_PARAM(0x04, 0x01),
+ ILI9806_SET_REG_PARAM(0x05, 0x01),
+ ILI9806_SET_REG_PARAM(0x06, 0x98),
+ ILI9806_SET_REG_PARAM(0x07, 0x06),
+ ILI9806_SET_REG_PARAM(0x08, 0x01),
+ ILI9806_SET_REG_PARAM(0x09, 0x80),
+ ILI9806_SET_REG_PARAM(0x0A, 0x00),
+ ILI9806_SET_REG_PARAM(0x0B, 0x00),
+ ILI9806_SET_REG_PARAM(0x0C, 0x01),
+ ILI9806_SET_REG_PARAM(0x0D, 0x01),
+ ILI9806_SET_REG_PARAM(0x0E, 0x00),
+ ILI9806_SET_REG_PARAM(0x0F, 0x00),
+ ILI9806_SET_REG_PARAM(0x10, 0xF0),
+ ILI9806_SET_REG_PARAM(0x11, 0xF4),
+ ILI9806_SET_REG_PARAM(0x12, 0x01),
+ ILI9806_SET_REG_PARAM(0x13, 0x00),
+ ILI9806_SET_REG_PARAM(0x14, 0x00),
+ ILI9806_SET_REG_PARAM(0x15, 0xC0),
+ ILI9806_SET_REG_PARAM(0x16, 0x08),
+ ILI9806_SET_REG_PARAM(0x17, 0x00),
+ ILI9806_SET_REG_PARAM(0x18, 0x00),
+ ILI9806_SET_REG_PARAM(0x19, 0x00),
+ ILI9806_SET_REG_PARAM(0x1A, 0x00),
+ ILI9806_SET_REG_PARAM(0x1B, 0x00),
+ ILI9806_SET_REG_PARAM(0x1C, 0x00),
+ ILI9806_SET_REG_PARAM(0x1D, 0x00),
+ ILI9806_SET_REG_PARAM(0x20, 0x01),
+ ILI9806_SET_REG_PARAM(0x21, 0x23),
+ ILI9806_SET_REG_PARAM(0x22, 0x45),
+ ILI9806_SET_REG_PARAM(0x23, 0x67),
+ ILI9806_SET_REG_PARAM(0x24, 0x01),
+ ILI9806_SET_REG_PARAM(0x25, 0x23),
+ ILI9806_SET_REG_PARAM(0x26, 0x45),
+ ILI9806_SET_REG_PARAM(0x27, 0x67),
+ ILI9806_SET_REG_PARAM(0x30, 0x11),
+ ILI9806_SET_REG_PARAM(0x31, 0x11),
+ ILI9806_SET_REG_PARAM(0x32, 0x00),
+ ILI9806_SET_REG_PARAM(0x33, 0xEE),
+ ILI9806_SET_REG_PARAM(0x34, 0xFF),
+ ILI9806_SET_REG_PARAM(0x35, 0xBB),
+ ILI9806_SET_REG_PARAM(0x36, 0xAA),
+ ILI9806_SET_REG_PARAM(0x37, 0xDD),
+ ILI9806_SET_REG_PARAM(0x38, 0xCC),
+ ILI9806_SET_REG_PARAM(0x39, 0x66),
+ ILI9806_SET_REG_PARAM(0x3A, 0x77),
+ ILI9806_SET_REG_PARAM(0x3B, 0x22),
+ ILI9806_SET_REG_PARAM(0x3C, 0x22),
+ ILI9806_SET_REG_PARAM(0x3D, 0x22),
+ ILI9806_SET_REG_PARAM(0x3E, 0x22),
+ ILI9806_SET_REG_PARAM(0x3F, 0x22),
+ ILI9806_SET_REG_PARAM(0x40, 0x22),
+ /* register doesn't exist on page 6? */
+ ILI9806_SET_REG_PARAM(0x52, 0x10),
+ /* doesn't make sense, not valid according to datasheet */
+ ILI9806_SET_REG_PARAM(0x53, 0x10),
+ /* doesn't make sense, not valid according to datasheet */
+ ILI9806_SET_REG_PARAM(0x54, 0x13),
+
+ ILI9806_SET_PAGE(7),
+
+ /* enable VREG */
+ ILI9806_SET_REG_PARAM(0x18, 0x1D),
+ /* enable VGL_REG */
+ ILI9806_SET_REG_PARAM(0x17, 0x22),
+ /* register doesn't exist on page 7? */
+ ILI9806_SET_REG_PARAM(0x02, 0x77),
+ /* register doesn't exist on page 7? */
+ ILI9806_SET_REG_PARAM(0x26, 0xB2),
+ /* register doesn't exist on page 7? */
+ ILI9806_SET_REG_PARAM(0xE1, 0x79),
+
+ ILI9806_SET_PAGE(0),
+
+ ILI9806_SET_REG_PARAM(MIPI_DCS_SET_PIXEL_FORMAT,
+ MIPI_DCS_PIXEL_FMT_18BIT << 4),
+ ILI9806_SET_REG_PARAM(MIPI_DCS_SET_TEAR_ON, 0x00),
+ ILI9806_SET_REG(MIPI_DCS_EXIT_SLEEP_MODE),
+};
+
+#define NUM_INIT_REGS ARRAY_SIZE(panel_init)
+
+static inline struct ili9806 *panel_to_ili9806(struct drm_panel *panel)
+{
+ return container_of(panel, struct ili9806, panel);
+}
+
+static int ili9806_write_msg(struct ili9806 *ctx, const struct ili9806e_msg *msg)
+{
+ struct spi_transfer xfer = { };
+ struct spi_message spi;
+ //u16 txbuf[] = { msg->, ILI9806_DATA | data };
+
+ spi_message_init(&spi);
+
+ xfer.tx_buf = msg->msg;
+ xfer.bits_per_word = 9;
+ xfer.len = sizeof(u16) * msg->len;
+
+ spi_message_add_tail(&xfer, &spi);
+ return spi_sync(ctx->spi, &spi);
+}
+
+static int ili9806e_write_msg_list(struct ili9806 *ctx,
+ const struct ili9806e_msg msgs[],
+ unsigned int num_msgs)
+{
+ int ret, i;
+
+ for (i = 0; i < num_msgs; i++) {
+ ret = ili9806_write_msg(ctx, &msgs[i]);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static const struct drm_display_mode ili9806e_480x800_mode = {
+ .clock = 32000,
+ .hdisplay = 480,
+ .hsync_start = 480 + 10,
+ .hsync_end = 480 + 10 + 16,
+ .htotal = 480 + 10 + 16 + 59,
+ .vdisplay = 800,
+ .vsync_start = 800 + 15,
+ .vsync_end = 800 + 15 + 113,
+ .vtotal = 800 + 15 + 113 + 15,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static int ili9806_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct ili9806 *ctx = panel_to_ili9806(panel);
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &ili9806e_480x800_mode);
+ if (!mode) {
+ dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
+ ili9806e_480x800_mode.hdisplay,
+ ili9806e_480x800_mode.vdisplay,
+ drm_mode_vrefresh(&ili9806e_480x800_mode));
+ return -ENOMEM;
+ }
+
+ drm_mode_set_name(mode);
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ connector->display_info.width_mm = 61;
+ connector->display_info.height_mm = 103;
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &ctx->bus_format, 1);
+ connector->display_info.bus_flags =
+ DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
+
+ return 1;
+}
+
+static int ili9806_prepare(struct drm_panel *panel)
+{
+ struct ili9806 *ctx = panel_to_ili9806(panel);
+ int ret;
+
+ ret = regulator_enable(ctx->power);
+ if (ret)
+ return ret;
+
+ ret = ili9806e_write_msg_list(ctx, panel_init, NUM_INIT_REGS);
+
+ return ret;
+}
+
+static int ili9806_enable(struct drm_panel *panel)
+{
+ struct ili9806 *ctx = panel_to_ili9806(panel);
+ const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_ON);
+ int ret;
+
+ ret = ili9806_write_msg(ctx, &msg);
+
+ return ret;
+}
+
+static int ili9806_disable(struct drm_panel *panel)
+{
+ struct ili9806 *ctx = panel_to_ili9806(panel);
+ const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_OFF);
+ int ret;
+
+ ret = ili9806_write_msg(ctx, &msg);
+
+ return ret;
+}
+
+static int ili9806_unprepare(struct drm_panel *panel)
+{
+ struct ili9806 *ctx = panel_to_ili9806(panel);
+ const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_ENTER_SLEEP_MODE);
+ int ret;
+
+ ret = ili9806_write_msg(ctx, &msg);
+
+ return ret;
+}
+
+static const struct drm_panel_funcs ili9806_drm_funcs = {
+ .disable = ili9806_disable,
+ .enable = ili9806_enable,
+ .get_modes = ili9806_get_modes,
+ .prepare = ili9806_prepare,
+ .unprepare = ili9806_unprepare,
+};
+
+static const struct of_device_id ili9806_of_match[] = {
+ { .compatible = "txw,txw397017s2",
+ .data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
+ }, {
+ .compatible = "pimoroni,hyperpixel4",
+ .data = (void *)MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
+ }, {
+ .compatible = "ilitek,ili9806e",
+ .data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, ili9806_of_match);
+
+static int ili9806_probe(struct spi_device *spi)
+{
+ const struct ili9806e_msg panel_reset[] = {
+ ILI9806_SET_PAGE(0),
+ ILI9806_SET_REG_PARAM(0x01, 0x00)
+ };
+ const struct of_device_id *id;
+ struct ili9806 *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ id = of_match_node(ili9806_of_match, spi->dev.of_node);
+ if (!id)
+ return -ENODEV;
+
+ ctx->bus_format = (u32)(uintptr_t)id->data;
+
+ spi_set_drvdata(spi, ctx);
+ ctx->spi = spi;
+
+ drm_panel_init(&ctx->panel, &spi->dev, &ili9806_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ ctx->power = devm_regulator_get(&spi->dev, "power");
+ if (IS_ERR(ctx->power))
+ return PTR_ERR(ctx->power);
+
+ ctx->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset)) {
+ dev_err(&spi->dev, "Couldn't get our reset line\n");
+ return PTR_ERR(ctx->reset);
+ }
+
+ /* Soft reset */
+ ili9806e_write_msg_list(ctx, panel_reset, ARRAY_SIZE(panel_reset));
+ msleep(200);
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ return 0;
+}
+
+static void ili9806_remove(struct spi_device *spi)
+{
+ struct ili9806 *ctx = spi_get_drvdata(spi);
+
+ drm_panel_remove(&ctx->panel);
+}
+
+static const struct spi_device_id ili9806_ids[] = {
+ { "txw397017s2", 0 },
+ { "ili9806e", 0 },
+ { "hyperpixel4", 0 },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(spi, ili9806_ids);
+
+static struct spi_driver ili9806_driver = {
+ .probe = ili9806_probe,
+ .remove = ili9806_remove,
+ .driver = {
+ .name = "ili9806e",
+ .of_match_table = ili9806_of_match,
+ },
+ .id_table = ili9806_ids,
+};
+module_spi_driver(ili9806_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("ili9806 LCD panel driver");
+MODULE_LICENSE("GPL v2");