brcm2708: update to latest patches from RPi Foundation
[openwrt/staging/chunkeey.git] / target / linux / brcm2708 / patches-4.19 / 950-0052-Add-Chris-Boot-s-i2c-driver.patch
diff --git a/target/linux/brcm2708/patches-4.19/950-0052-Add-Chris-Boot-s-i2c-driver.patch b/target/linux/brcm2708/patches-4.19/950-0052-Add-Chris-Boot-s-i2c-driver.patch
new file mode 100644 (file)
index 0000000..cf26302
--- /dev/null
@@ -0,0 +1,660 @@
+From 1f72dfe2738305c57605a8192176117e1641779c Mon Sep 17 00:00:00 2001
+From: popcornmix <popcornmix@gmail.com>
+Date: Wed, 17 Jun 2015 15:44:08 +0100
+Subject: [PATCH] Add Chris Boot's i2c driver
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+i2c-bcm2708: fixed baudrate
+
+Fixed issue where the wrong CDIV value was set for baudrates below 3815 Hz (for 250MHz bus clock).
+In that case the computed CDIV value was more than 0xffff. However the CDIV register width is only 16 bits.
+This resulted in incorrect setting of CDIV and higher baudrate than intended.
+Example: 3500Hz -> CDIV=0x11704 -> CDIV(16bit)=0x1704 -> 42430Hz
+After correction: 3500Hz -> CDIV=0x11704 -> CDIV(16bit)=0xffff -> 3815Hz
+The correct baudrate is shown in the log after the cdiv > 0xffff correction.
+
+Perform I2C combined transactions when possible
+
+Perform I2C combined transactions whenever possible, within the
+restrictions of the Broadcomm Serial Controller.
+
+Disable DONE interrupt during TA poll
+
+Prevent interrupt from being triggered if poll is missed and transfer
+starts and finishes.
+
+i2c: Make combined transactions optional and disabled by default
+
+i2c: bcm2708: add device tree support
+
+Add DT support to driver and add to .dtsi file.
+Setup pins in .dts file.
+i2c is disabled by default.
+
+Signed-off-by: Noralf Tronnes <notro@tronnes.org>
+
+bcm2708: don't register i2c controllers when using DT
+
+The devices for the i2c controllers are in the Device Tree.
+Only register devices when not using DT.
+
+Signed-off-by: Noralf Tronnes <notro@tronnes.org>
+
+I2C: Only register the I2C device for the current board revision
+
+i2c_bcm2708: Fix clock reference counting
+
+Fix grabbing lock from atomic context in i2c driver
+
+2 main changes:
+- check for timeouts in the bcm2708_bsc_setup function as indicated by this comment:
+      /* poll for transfer start bit (should only take 1-20 polls) */
+  This implies that the setup function can now fail so account for this everywhere it's called
+- Removed the clk_get_rate call from inside the setup function as it locks a mutex and that's not ok since we call it from under a spin lock.
+
+i2c-bcm2708: When using DT, leave the GPIO setup to pinctrl
+
+i2c-bcm2708: Increase timeouts to allow larger transfers
+
+Use the timeout value provided by the I2C_TIMEOUT ioctl when waiting
+for completion. The default timeout is 1 second.
+
+See: https://github.com/raspberrypi/linux/issues/260
+
+i2c-bcm2708/BCM270X_DT: Add support for I2C2
+
+The third I2C bus (I2C2) is normally reserved for HDMI use. Careless
+use of this bus can break an attached display - use with caution.
+
+It is recommended to disable accesses by VideoCore by setting
+hdmi_ignore_edid=1 or hdmi_edid_file=1 in config.txt.
+
+The interface is disabled by default - enable using the
+i2c2_iknowwhatimdoing DT parameter.
+
+bcm2708-spi: Don't use static pin configuration with DT
+
+Also remove superfluous error checking - the SPI framework ensures the
+validity of the chip_select value.
+
+i2c-bcm2708: Remove non-DT support
+
+Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
+
+Set the BSC_CLKT clock streching timeout to 35ms as per SMBus specs.
+
+Fixes i2c_bcm2708: Write to FIFO correctly - v2 (#1574)
+
+* i2c: fix i2c_bcm2708: Clear FIFO before sending data
+
+Make sure FIFO gets cleared before trying to send
+data in case of a repeated start (COMBINED=Y).
+
+* i2c: fix i2c_bcm2708: Only write to FIFO when not full
+
+Check if FIFO can accept data before writing.
+To avoid a peripheral read on the last iteration of a loop,
+both bcm2708_bsc_fifo_fill and ~drain are changed as well.
+---
+ drivers/i2c/busses/Kconfig       |  19 ++
+ drivers/i2c/busses/Makefile      |   2 +
+ drivers/i2c/busses/i2c-bcm2708.c | 512 +++++++++++++++++++++++++++++++
+ 3 files changed, 533 insertions(+)
+ create mode 100644 drivers/i2c/busses/i2c-bcm2708.c
+
+--- a/drivers/i2c/busses/Kconfig
++++ b/drivers/i2c/busses/Kconfig
+@@ -8,6 +8,25 @@ menu "I2C Hardware Bus support"
+ comment "PC SMBus host controller drivers"
+       depends on PCI
++config I2C_BCM2708
++      tristate "BCM2708 BSC"
++      depends on ARCH_BCM2835
++      help
++        Enabling this option will add BSC (Broadcom Serial Controller)
++        support for the BCM2708. BSC is a Broadcom proprietary bus compatible
++        with I2C/TWI/SMBus.
++
++config I2C_BCM2708_BAUDRATE
++      prompt "BCM2708 I2C baudrate"
++      depends on I2C_BCM2708
++      int
++      default 100000
++      help
++        Set the I2C baudrate. This will alter the default value. A
++        different baudrate can be set by using a module parameter as well. If
++        no parameter is provided when loading, this is the value that will be
++        used.
++
+ config I2C_ALI1535
+       tristate "ALI 1535"
+       depends on PCI
+--- a/drivers/i2c/busses/Makefile
++++ b/drivers/i2c/busses/Makefile
+@@ -3,6 +3,8 @@
+ # Makefile for the i2c bus drivers.
+ #
++obj-$(CONFIG_I2C_BCM2708)     += i2c-bcm2708.o
++
+ # ACPI drivers
+ obj-$(CONFIG_I2C_SCMI)                += i2c-scmi.o
+--- /dev/null
++++ b/drivers/i2c/busses/i2c-bcm2708.c
+@@ -0,0 +1,512 @@
++/*
++ * Driver for Broadcom BCM2708 BSC Controllers
++ *
++ * Copyright (C) 2012 Chris Boot & Frank Buss
++ *
++ * This driver is inspired by:
++ * i2c-ocores.c, by Peter Korsgaard <jacmet@sunsite.dk>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++ */
++
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/spinlock.h>
++#include <linux/clk.h>
++#include <linux/err.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/io.h>
++#include <linux/slab.h>
++#include <linux/i2c.h>
++#include <linux/interrupt.h>
++#include <linux/sched.h>
++#include <linux/wait.h>
++
++/* BSC register offsets */
++#define BSC_C                 0x00
++#define BSC_S                 0x04
++#define BSC_DLEN              0x08
++#define BSC_A                 0x0c
++#define BSC_FIFO              0x10
++#define BSC_DIV                       0x14
++#define BSC_DEL                       0x18
++#define BSC_CLKT              0x1c
++
++/* Bitfields in BSC_C */
++#define BSC_C_I2CEN           0x00008000
++#define BSC_C_INTR            0x00000400
++#define BSC_C_INTT            0x00000200
++#define BSC_C_INTD            0x00000100
++#define BSC_C_ST              0x00000080
++#define BSC_C_CLEAR_1         0x00000020
++#define BSC_C_CLEAR_2         0x00000010
++#define BSC_C_READ            0x00000001
++
++/* Bitfields in BSC_S */
++#define BSC_S_CLKT            0x00000200
++#define BSC_S_ERR             0x00000100
++#define BSC_S_RXF             0x00000080
++#define BSC_S_TXE             0x00000040
++#define BSC_S_RXD             0x00000020
++#define BSC_S_TXD             0x00000010
++#define BSC_S_RXR             0x00000008
++#define BSC_S_TXW             0x00000004
++#define BSC_S_DONE            0x00000002
++#define BSC_S_TA              0x00000001
++
++#define I2C_WAIT_LOOP_COUNT   200
++
++#define DRV_NAME              "bcm2708_i2c"
++
++static unsigned int baudrate;
++module_param(baudrate, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
++MODULE_PARM_DESC(baudrate, "The I2C baudrate");
++
++static bool combined = false;
++module_param(combined, bool, 0644);
++MODULE_PARM_DESC(combined, "Use combined transactions");
++
++struct bcm2708_i2c {
++      struct i2c_adapter adapter;
++
++      spinlock_t lock;
++      void __iomem *base;
++      int irq;
++      struct clk *clk;
++      u32 cdiv;
++      u32 clk_tout;
++
++      struct completion done;
++
++      struct i2c_msg *msg;
++      int pos;
++      int nmsgs;
++      bool error;
++};
++
++static inline u32 bcm2708_rd(struct bcm2708_i2c *bi, unsigned reg)
++{
++      return readl(bi->base + reg);
++}
++
++static inline void bcm2708_wr(struct bcm2708_i2c *bi, unsigned reg, u32 val)
++{
++      writel(val, bi->base + reg);
++}
++
++static inline void bcm2708_bsc_reset(struct bcm2708_i2c *bi)
++{
++      bcm2708_wr(bi, BSC_C, 0);
++      bcm2708_wr(bi, BSC_S, BSC_S_CLKT | BSC_S_ERR | BSC_S_DONE);
++}
++
++static inline void bcm2708_bsc_fifo_drain(struct bcm2708_i2c *bi)
++{
++      while ((bi->pos < bi->msg->len) && (bcm2708_rd(bi, BSC_S) & BSC_S_RXD))
++              bi->msg->buf[bi->pos++] = bcm2708_rd(bi, BSC_FIFO);
++}
++
++static inline void bcm2708_bsc_fifo_fill(struct bcm2708_i2c *bi)
++{
++      while ((bi->pos < bi->msg->len) && (bcm2708_rd(bi, BSC_S) & BSC_S_TXD))
++              bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
++}
++
++static inline int bcm2708_bsc_setup(struct bcm2708_i2c *bi)
++{
++      u32 cdiv, s, clk_tout;
++      u32 c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_ST | BSC_C_CLEAR_1;
++      int wait_loops = I2C_WAIT_LOOP_COUNT;
++
++      /* Can't call clk_get_rate as it locks a mutex and here we are spinlocked.
++       * Use the value that we cached in the probe.
++       */
++      cdiv = bi->cdiv;
++      clk_tout = bi->clk_tout;
++
++      if (bi->msg->flags & I2C_M_RD)
++              c |= BSC_C_INTR | BSC_C_READ;
++      else
++              c |= BSC_C_INTT;
++
++      bcm2708_wr(bi, BSC_CLKT, clk_tout);
++      bcm2708_wr(bi, BSC_DIV, cdiv);
++      bcm2708_wr(bi, BSC_A, bi->msg->addr);
++      bcm2708_wr(bi, BSC_DLEN, bi->msg->len);
++      if (combined)
++      {
++              /* Do the next two messages meet combined transaction criteria?
++                 - Current message is a write, next message is a read
++                 - Both messages to same slave address
++                 - Write message can fit inside FIFO (16 bytes or less) */
++              if ( (bi->nmsgs > 1) &&
++                      !(bi->msg[0].flags & I2C_M_RD) && (bi->msg[1].flags & I2C_M_RD) &&
++                       (bi->msg[0].addr == bi->msg[1].addr) && (bi->msg[0].len <= 16)) {
++
++                      /* Clear FIFO */
++                      bcm2708_wr(bi, BSC_C, BSC_C_CLEAR_1);
++
++                      /* Fill FIFO with entire write message (16 byte FIFO) */
++                      while (bi->pos < bi->msg->len) {
++                              bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
++                      }
++                      /* Start write transfer (no interrupts, don't clear FIFO) */
++                      bcm2708_wr(bi, BSC_C, BSC_C_I2CEN | BSC_C_ST);
++
++                      /* poll for transfer start bit (should only take 1-20 polls) */
++                      do {
++                              s = bcm2708_rd(bi, BSC_S);
++                      } while (!(s & (BSC_S_TA | BSC_S_ERR | BSC_S_CLKT | BSC_S_DONE)) && --wait_loops >= 0);
++
++                      /* did we time out or some error occured? */
++                      if (wait_loops < 0 || (s & (BSC_S_ERR | BSC_S_CLKT))) {
++                              return -1;
++                      }
++
++                      /* Send next read message before the write transfer finishes. */
++                      bi->nmsgs--;
++                      bi->msg++;
++                      bi->pos = 0;
++                      bcm2708_wr(bi, BSC_DLEN, bi->msg->len);
++                      c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_INTR | BSC_C_ST | BSC_C_READ;
++              }
++      }
++      bcm2708_wr(bi, BSC_C, c);
++
++      return 0;
++}
++
++static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id)
++{
++      struct bcm2708_i2c *bi = dev_id;
++      bool handled = true;
++      u32 s;
++      int ret;
++
++      spin_lock(&bi->lock);
++
++      /* we may see camera interrupts on the "other" I2C channel
++                 Just return if we've not sent anything */
++      if (!bi->nmsgs || !bi->msg) {
++              goto early_exit;
++      }
++
++      s = bcm2708_rd(bi, BSC_S);
++
++      if (s & (BSC_S_CLKT | BSC_S_ERR)) {
++              bcm2708_bsc_reset(bi);
++              bi->error = true;
++
++              bi->msg = 0; /* to inform the that all work is done */
++              bi->nmsgs = 0;
++              /* wake up our bh */
++              complete(&bi->done);
++      } else if (s & BSC_S_DONE) {
++              bi->nmsgs--;
++
++              if (bi->msg->flags & I2C_M_RD) {
++                      bcm2708_bsc_fifo_drain(bi);
++              }
++
++              bcm2708_bsc_reset(bi);
++
++              if (bi->nmsgs) {
++                      /* advance to next message */
++                      bi->msg++;
++                      bi->pos = 0;
++                      ret = bcm2708_bsc_setup(bi);
++                      if (ret < 0) {
++                              bcm2708_bsc_reset(bi);
++                              bi->error = true;
++                              bi->msg = 0; /* to inform the that all work is done */
++                              bi->nmsgs = 0;
++                              /* wake up our bh */
++                              complete(&bi->done);
++                              goto early_exit;
++                      }
++              } else {
++                      bi->msg = 0; /* to inform the that all work is done */
++                      bi->nmsgs = 0;
++                      /* wake up our bh */
++                      complete(&bi->done);
++              }
++      } else if (s & BSC_S_TXW) {
++              bcm2708_bsc_fifo_fill(bi);
++      } else if (s & BSC_S_RXR) {
++              bcm2708_bsc_fifo_drain(bi);
++      } else {
++              handled = false;
++      }
++
++early_exit:
++      spin_unlock(&bi->lock);
++
++      return handled ? IRQ_HANDLED : IRQ_NONE;
++}
++
++static int bcm2708_i2c_master_xfer(struct i2c_adapter *adap,
++      struct i2c_msg *msgs, int num)
++{
++      struct bcm2708_i2c *bi = adap->algo_data;
++      unsigned long flags;
++      int ret;
++
++      spin_lock_irqsave(&bi->lock, flags);
++
++      reinit_completion(&bi->done);
++      bi->msg = msgs;
++      bi->pos = 0;
++      bi->nmsgs = num;
++      bi->error = false;
++
++      ret = bcm2708_bsc_setup(bi);
++
++      spin_unlock_irqrestore(&bi->lock, flags);
++
++      /* check the result of the setup */
++      if (ret < 0)
++      {
++              dev_err(&adap->dev, "transfer setup timed out\n");
++              goto error_timeout;
++      }
++
++      ret = wait_for_completion_timeout(&bi->done, adap->timeout);
++      if (ret == 0) {
++              dev_err(&adap->dev, "transfer timed out\n");
++              goto error_timeout;
++      }
++
++      ret = bi->error ? -EIO : num;
++      return ret;
++
++error_timeout:
++      spin_lock_irqsave(&bi->lock, flags);
++      bcm2708_bsc_reset(bi);
++      bi->msg = 0; /* to inform the interrupt handler that there's nothing else to be done */
++      bi->nmsgs = 0;
++      spin_unlock_irqrestore(&bi->lock, flags);
++      return -ETIMEDOUT;
++}
++
++static u32 bcm2708_i2c_functionality(struct i2c_adapter *adap)
++{
++      return I2C_FUNC_I2C | /*I2C_FUNC_10BIT_ADDR |*/ I2C_FUNC_SMBUS_EMUL;
++}
++
++static struct i2c_algorithm bcm2708_i2c_algorithm = {
++      .master_xfer = bcm2708_i2c_master_xfer,
++      .functionality = bcm2708_i2c_functionality,
++};
++
++static int bcm2708_i2c_probe(struct platform_device *pdev)
++{
++      struct resource *regs;
++      int irq, err = -ENOMEM;
++      struct clk *clk;
++      struct bcm2708_i2c *bi;
++      struct i2c_adapter *adap;
++      unsigned long bus_hz;
++      u32 cdiv, clk_tout;
++      u32 baud;
++
++      baud = CONFIG_I2C_BCM2708_BAUDRATE;
++
++      if (pdev->dev.of_node) {
++              u32 bus_clk_rate;
++              pdev->id = of_alias_get_id(pdev->dev.of_node, "i2c");
++              if (pdev->id < 0) {
++                      dev_err(&pdev->dev, "alias is missing\n");
++                      return -EINVAL;
++              }
++              if (!of_property_read_u32(pdev->dev.of_node,
++                                      "clock-frequency", &bus_clk_rate))
++                      baud = bus_clk_rate;
++              else
++                      dev_warn(&pdev->dev,
++                              "Could not read clock-frequency property\n");
++      }
++
++      if (baudrate)
++              baud = baudrate;
++
++      regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++      if (!regs) {
++              dev_err(&pdev->dev, "could not get IO memory\n");
++              return -ENXIO;
++      }
++
++      irq = platform_get_irq(pdev, 0);
++      if (irq < 0) {
++              dev_err(&pdev->dev, "could not get IRQ\n");
++              return irq;
++      }
++
++      clk = clk_get(&pdev->dev, NULL);
++      if (IS_ERR(clk)) {
++              dev_err(&pdev->dev, "could not find clk: %ld\n", PTR_ERR(clk));
++              return PTR_ERR(clk);
++      }
++
++      err = clk_prepare_enable(clk);
++      if (err) {
++              dev_err(&pdev->dev, "could not enable clk: %d\n", err);
++              goto out_clk_put;
++      }
++
++      bi = kzalloc(sizeof(*bi), GFP_KERNEL);
++      if (!bi)
++              goto out_clk_disable;
++
++      platform_set_drvdata(pdev, bi);
++
++      adap = &bi->adapter;
++      adap->class = I2C_CLASS_HWMON | I2C_CLASS_DDC;
++      adap->algo = &bcm2708_i2c_algorithm;
++      adap->algo_data = bi;
++      adap->dev.parent = &pdev->dev;
++      adap->nr = pdev->id;
++      strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
++      adap->dev.of_node = pdev->dev.of_node;
++
++      switch (pdev->id) {
++      case 0:
++              adap->class = I2C_CLASS_HWMON;
++              break;
++      case 1:
++              adap->class = I2C_CLASS_DDC;
++              break;
++      case 2:
++              adap->class = I2C_CLASS_DDC;
++              break;
++      default:
++              dev_err(&pdev->dev, "can only bind to BSC 0, 1 or 2\n");
++              err = -ENXIO;
++              goto out_free_bi;
++      }
++
++      spin_lock_init(&bi->lock);
++      init_completion(&bi->done);
++
++      bi->base = ioremap(regs->start, resource_size(regs));
++      if (!bi->base) {
++              dev_err(&pdev->dev, "could not remap memory\n");
++              goto out_free_bi;
++      }
++
++      bi->irq = irq;
++      bi->clk = clk;
++
++      err = request_irq(irq, bcm2708_i2c_interrupt, IRQF_SHARED,
++                      dev_name(&pdev->dev), bi);
++      if (err) {
++              dev_err(&pdev->dev, "could not request IRQ: %d\n", err);
++              goto out_iounmap;
++      }
++
++      bcm2708_bsc_reset(bi);
++
++      err = i2c_add_numbered_adapter(adap);
++      if (err < 0) {
++              dev_err(&pdev->dev, "could not add I2C adapter: %d\n", err);
++              goto out_free_irq;
++      }
++
++      bus_hz = clk_get_rate(bi->clk);
++      cdiv = bus_hz / baud;
++      if (cdiv > 0xffff) {
++              cdiv = 0xffff;
++              baud = bus_hz / cdiv;
++      }
++
++      clk_tout = 35/1000*baud; //35ms timeout as per SMBus specs.
++      if (clk_tout > 0xffff)
++              clk_tout = 0xffff;
++      
++      bi->cdiv = cdiv;
++      bi->clk_tout = clk_tout;
++
++      dev_info(&pdev->dev, "BSC%d Controller at 0x%08lx (irq %d) (baudrate %d)\n",
++              pdev->id, (unsigned long)regs->start, irq, baud);
++
++      return 0;
++
++out_free_irq:
++      free_irq(bi->irq, bi);
++out_iounmap:
++      iounmap(bi->base);
++out_free_bi:
++      kfree(bi);
++out_clk_disable:
++      clk_disable_unprepare(clk);
++out_clk_put:
++      clk_put(clk);
++      return err;
++}
++
++static int bcm2708_i2c_remove(struct platform_device *pdev)
++{
++      struct bcm2708_i2c *bi = platform_get_drvdata(pdev);
++
++      platform_set_drvdata(pdev, NULL);
++
++      i2c_del_adapter(&bi->adapter);
++      free_irq(bi->irq, bi);
++      iounmap(bi->base);
++      clk_disable_unprepare(bi->clk);
++      clk_put(bi->clk);
++      kfree(bi);
++
++      return 0;
++}
++
++static const struct of_device_id bcm2708_i2c_of_match[] = {
++        { .compatible = "brcm,bcm2708-i2c" },
++        {},
++};
++MODULE_DEVICE_TABLE(of, bcm2708_i2c_of_match);
++
++static struct platform_driver bcm2708_i2c_driver = {
++      .driver         = {
++              .name   = DRV_NAME,
++              .owner  = THIS_MODULE,
++              .of_match_table = bcm2708_i2c_of_match,
++      },
++      .probe          = bcm2708_i2c_probe,
++      .remove         = bcm2708_i2c_remove,
++};
++
++// module_platform_driver(bcm2708_i2c_driver);
++
++
++static int __init bcm2708_i2c_init(void)
++{
++      return platform_driver_register(&bcm2708_i2c_driver);
++}
++
++static void __exit bcm2708_i2c_exit(void)
++{
++      platform_driver_unregister(&bcm2708_i2c_driver);
++}
++
++module_init(bcm2708_i2c_init);
++module_exit(bcm2708_i2c_exit);
++
++
++
++MODULE_DESCRIPTION("BSC controller driver for Broadcom BCM2708");
++MODULE_AUTHOR("Chris Boot <bootc@bootc.net>");
++MODULE_LICENSE("GPL v2");
++MODULE_ALIAS("platform:" DRV_NAME);