lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1389010062-17185-3-git-send-email-satish.patel@ti.com>
Date:	Mon, 6 Jan 2014 17:37:39 +0530
From:	Satish Patel <satish.patel@...com>
To:	<linux-kernel@...r.kernel.org>,
	<linux-arm-kernel@...ts.infradead.org>,
	<linux-omap@...r.kernel.org>, <devicetree@...r.kernel.org>
CC:	<rob@...dley.net>, <gregkh@...uxfoundation.org>, <arnd@...db.de>
Subject: [RFC PATCH v1 2/5] misc: tda8026: Add NXP TDA8026 PHY driver

TDA8026 is a SmartCard PHY from NXP.

The PHY interfaces with the main processor over the
I2C interface and acts as a slave device.

The driver also exposes the phy interface
(defined@...lude/linux/sc_phy.h) for SmartCard controller.
Controller uses this interface to communicate with smart card
inserted to the phy's slot.

Note: gpio irq is not validated as I do not have device with that.
I have validated interrupt with dedicated interrupt line on my device.

Signed-off-by: Maulik Mankad <maulik@...com>
Signed-off-by: Satish Patel <satish.patel@...com>
---
 Documentation/devicetree/bindings/misc/tda8026.txt |   14 +
 drivers/misc/Kconfig                               |    7 +
 drivers/misc/Makefile                              |    1 +
 drivers/misc/tda8026.c                             | 1271 ++++++++++++++++++++
 4 files changed, 1293 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/misc/tda8026.txt
 create mode 100644 drivers/misc/tda8026.c

diff --git a/Documentation/devicetree/bindings/misc/tda8026.txt b/Documentation/devicetree/bindings/misc/tda8026.txt
new file mode 100644
index 0000000..d3083bf
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/tda8026.txt
@@ -0,0 +1,14 @@
+TDA8026 smart card slot interface
+
+Required properties:
+- compatible: nxp,tda8026
+- shutdown-gpio = GPIO pin mapping for SDWNN pin
+- reg = i2c interface address
+
+
+Example:
+tda8026: tda8026@48 {
+		 compatible = "nxp,tda8026";
+		 reg = <0x48>;
+		 shutdown-gpio = <&gpio5 19 GPIO_ACTIVE_HIGH>;/* Bank5, pin19 */
+	 };
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index f1da896..6bbc1c7 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -536,6 +536,13 @@ config CROSSBAR
 	  muxing the irq/dma requests from external peripherals to the corresponding
 	  controller's inputs.
 
+config NXP_TDA8026_PHY
+        tristate "NXP PHY Driver for Smart Card PHY"
+        depends on I2C=y
+        help
+          If you say yes here you get support for the TDA8026 Smart card PHY
+	  with I2C interface.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 37ce1b8..853b225 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -54,3 +54,4 @@ obj-$(CONFIG_VMWARE_VMCI)	+= vmw_vmci/
 obj-$(CONFIG_LATTICE_ECP3_CONFIG)	+= lattice-ecp3-config.o
 obj-$(CONFIG_SRAM)		+= sram.o
 obj-$(CONFIG_CROSSBAR)		+= crossbar.o
+obj-$(CONFIG_NXP_TDA8026_PHY)	+= tda8026.o
diff --git a/drivers/misc/tda8026.c b/drivers/misc/tda8026.c
new file mode 100644
index 0000000..b24e948
--- /dev/null
+++ b/drivers/misc/tda8026.c
@@ -0,0 +1,1271 @@
+/*
+ * tda8026.c - TDA8026 PHY driver for NXP Smart card PHY
+ *
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/notifier.h>
+#include <linux/sc_phy.h>
+#include <linux/of_gpio.h>
+#include <linux/of_device.h>
+#include <linux/delay.h>
+
+#define TDA8026_MAX_SLOTS		(5)
+#define TDA8026_NUM_SAM_SLOTS		(4)
+#define TDA8026_USERCARD_SLOT		(1)
+
+#define TDA8026_CSB_ADDR		(0x24)
+#define TDA8026_REG0_ADDR		(0x20)
+#define TDA8026_REG1_ADDR		(0x21)
+#define TDA8026_SLEWRATE_ADDR		(0x20)
+#define TDA8026_PRODVER_ADDR		(0x20)
+#define TDA8026_INTSTAT_ADDR		(0x21)
+
+#define TDA8026_PHY_PRODUCT_VERSION	(0xC2)
+
+/* CSB register values */
+#define TDA8026_CSB_PV_INTSTAT_VAL	(0x0)
+#define TDA8026_CSB_SLEWRATE_VAL	(0x6)
+
+/* Slot REG0 read mode bit fields */
+#define TDA8026_REG0_ACTIVE_MASK	(0x80)
+#define TDA8026_REG0_EARLY_MASK		(0x40)
+#define TDA8026_REG0_MUTE_MASK		(0x20)
+#define TDA8026_REG0_PROT_MASK		(0x10)
+#define TDA8026_REG0_SUPL_MASK		(0x08)
+#define TDA8026_REG0_CLKSW_MASK		(0x04)
+#define TDA8026_REG0_PREL_MASK		(0x02)
+#define TDA8026_REG0_PRES_MASK		(0x01)
+
+/* Slot REG0 write mode bit fields */
+#define TDA8026_REG0_VCC1V8_MASK	(0x80)
+#define TDA8026_REG0_IOEN_MASK		(0x40)
+
+#define TDA8026_REG0_REG10_MASK		(0x30)
+#define TDA8026_REG0_REG10_SHIFT	(4)
+#define TDA8026_REG0_REG10_CFG_VAL	(0x0)
+#define TDA8026_REG0_REG10_D_VAL	(0x1)
+#define TDA8026_REG0_REG10_CMSB_VAL	(0x2)
+#define TDA8026_REG0_REG10_CLSB_VAL	(0x3)
+
+#define TDA8026_REG0_PDWN_MASK		(0x08)
+#define TDA8026_REG0_5V3VN_MASK		(0x04)
+#define TDA8026_REG0_WARM_RESET_MASK	(0x02)
+#define TDA8026_REG0_START_MASK		(0x01)
+
+/* Slot REG1 CFG bit fields REG[1:0] = 00b */
+#define TDA8026_REG1CFG_CFGP2_MASK	(0x80)
+#define TDA8026_REG1CFG_RSTIN_MASK	(0x40)
+#define TDA8026_REG1CFG_C8_MASK		(0x20)
+#define TDA8026_REG1CFG_C4_MASK		(0x10)
+#define TDA8026_REG1CFG_CLKPD_MASK	(0x0C)
+#define TDA8026_REG1CFG_CLKPD_SHIFT	(2)
+#define TDA8026_REG1CFG_CLKDIV_MASK	(0x03)
+#define TDA8026_REG1CFG_CLKDIV_SHIFT	(0)
+
+/* Slew rate register bit fields */
+#define TDA8026_SR_CLKSR_SLOT0_MASK	0x0C
+#define TDA8026_SR_CLKSR_SLOT0_SHIFT	0x2
+#define TDA8026_SR_IOSR_SLOT0_MASK	0x3
+#define TDA8026_SR_IOSR_SLOT0_SHIFT	0x0
+
+#define TDA8026_SR_CLKSR_SLOT2TO5_MASK	0xC0
+#define TDA8026_SR_CLKSR_SLOT2TO5_SHIFT	0x6
+#define TDA8026_SR_IOSR_SLOT2TO5_MASK	0x30
+#define TDA8026_SR_IOSR_SLOT2TO5_SHIFT	0x4
+
+#define TDA8026_MIN_EARLY_CYCLE    (200)
+
+struct tda8026 {
+	/* device pointer */
+	struct device *dev;
+
+	/* For device IO interfaces: I2C or SPI */
+	void *control_data;
+	/* Store a shadow of Slot Register 0 as it cannot be read */
+	u8 reg0[TDA8026_MAX_SLOTS];
+	int irq;
+	int notify;
+	int shutdown_gpio;
+	int enable;
+};
+
+static BLOCKING_NOTIFIER_HEAD(tda8026_notifier_list);
+
+static int tda8026_i2c_read(struct tda8026 *tda8026, u8 reg,
+		int bytes, u8 *dest)
+{
+	struct i2c_client *i2c = tda8026->control_data;
+	struct i2c_msg xfer;
+	int ret;
+
+	/* We need to read from the slave address itself, as there
+	 * is no separate register to be accessed in TDA8026
+	 */
+
+	xfer.addr = reg;
+	xfer.flags = I2C_M_RD;
+	xfer.len = bytes;
+	xfer.buf = dest;
+
+	ret = i2c_transfer(i2c->adapter, &xfer, 1);
+	if (ret < 0)
+		dev_err(tda8026->dev, "Read [0x%x] Error %d\n", reg, ret);
+
+	return ret;
+}
+
+static int tda8026_i2c_write(struct tda8026 *tda8026, u8 reg,
+		int bytes, u8 *src)
+{
+	struct i2c_client *i2c = tda8026->control_data;
+	struct i2c_msg xfer;
+	int ret;
+
+	/* We have to write to the slave address itself, as
+	 * there is no separate register to be accessed in TDA8026
+	 */
+	xfer.addr = reg;
+	xfer.flags = 0;
+	xfer.len = bytes;
+	xfer.buf = src;
+
+	ret = i2c_transfer(i2c->adapter, &xfer, 1);
+	if (ret < 0)
+		dev_err(tda8026->dev, "Write [0x%x] Error %d\n", reg, ret);
+
+	return ret;
+}
+
+/* put the phy in shutdown mode, which in turn deactivate
+ * all the cards in the slot and card pins are forced to 0V
+ */
+static inline void tda8026_disable(struct tda8026 *tda8026)
+{
+	dev_dbg(tda8026->dev, "tda8026_disable!!!!");
+	tda8026->enable = 0;
+	if (gpio_is_valid(tda8026->shutdown_gpio))
+		gpio_set_value(tda8026->shutdown_gpio, 0);
+}
+
+/* exit from shutdown mode */
+static inline void tda8026_enable(struct tda8026 *tda8026)
+{
+	dev_dbg(tda8026->dev, "tda8026_enable!!!!");
+	tda8026->enable = 1;
+	if (gpio_is_valid(tda8026->shutdown_gpio))
+		gpio_set_value(tda8026->shutdown_gpio, 1);
+	/* Added dealy to stabilized phy state after pulling shutdown line */
+	mdelay(100);
+}
+
+/*
+ * Select the card slot to communicate with the card
+ * Note that card slots are numbered from 0 in software.
+ * However TDA8026 PHY numbers the slot starting 1.
+ */
+static inline int tda8026_select_slot(struct tda8026 *tda8026, u8 slot)
+{
+	int ret = 0;
+
+	/* In SW slot starts from 0, in TDA8026 it starts from 1 */
+	slot = slot + 1;
+	ret = tda8026_i2c_write(tda8026, TDA8026_CSB_ADDR, 1, &slot);
+
+	return ret;
+}
+
+static int tda8026_clear_interrupts(struct tda8026 *tda8026)
+{
+	u8 val;
+	u8 status;
+	int ret = 0;
+
+	/* Select the Interrupt register bank */
+	val = TDA8026_CSB_PV_INTSTAT_VAL;
+	tda8026_i2c_write(tda8026, TDA8026_CSB_ADDR, 1, &val);
+
+	/* Read the interrupt status which will tell us the slot */
+	ret = tda8026_i2c_read(tda8026, TDA8026_INTSTAT_ADDR, 1, &status);
+	if (ret < 0)
+		return ret;
+
+	for (val = 1; val > TDA8026_MAX_SLOTS; val++) {
+		/* Program the slot number to the CSB register */
+		ret = tda8026_i2c_write(tda8026, TDA8026_CSB_ADDR, 1, &val);
+		if (ret < 0)
+			return ret;
+
+		ret = tda8026_i2c_read(tda8026, TDA8026_REG0_ADDR, 1, &status);
+		if (ret < 0)
+			return ret;
+	}
+	return ret;
+}
+/*
+ * TDA8026 PHY IRQ handler
+ */
+static irqreturn_t tda8026_irq(int irq, void *irq_data)
+{
+	struct sc_phy *phy_tda8026 = (struct sc_phy *)irq_data;
+	struct tda8026 *tda8026	= (struct tda8026 *)phy_tda8026->pdata;
+	u8 slot;
+	u8 val;
+	u8 status;
+	int ret = 0;
+	int action = 0;
+
+	dev_dbg(tda8026->dev, "tda8026_irq!!");
+
+	if (tda8026->enable == 0) {
+		dev_dbg(tda8026->dev, "phy is disable not serving interrputs!!");
+		/* when, phy is in shutdown mode, it can detect the card insert
+		 * event. But if phy is not enable (i.e.) there is no consumer
+		 * of phy then just enable phy, clear the interrupt and disable
+		 * again
+		 */
+		tda8026_enable(tda8026);
+		tda8026_clear_interrupts(tda8026);
+		tda8026_disable(tda8026);
+		return IRQ_HANDLED;
+	}
+
+	/* Select the Interrupt register bank */
+	val = TDA8026_CSB_PV_INTSTAT_VAL;
+	tda8026_i2c_write(tda8026, TDA8026_CSB_ADDR, 1, &val);
+
+	/* Read the interrupt status which will tell us the slot */
+	ret = tda8026_i2c_read(tda8026, TDA8026_INTSTAT_ADDR, 1, &status);
+	if (ret < 0)
+		return IRQ_HANDLED;
+
+
+	/* find out for which slot interrupt has occur */
+	slot = 0;
+	while (val == 0 && slot < TDA8026_MAX_SLOTS) {
+		val = status & (1 << slot);
+		slot++;
+	}
+
+	if (slot > TDA8026_MAX_SLOTS) {
+		dev_err(tda8026->dev, "invalid slot interrput");
+		return IRQ_HANDLED;
+	}
+
+	/* Program the slot number to the CSB register */
+	ret = tda8026_i2c_write(tda8026, TDA8026_CSB_ADDR, 1, &slot);
+	if (ret < 0)
+		return IRQ_HANDLED;
+
+	/* Now read the slot reg0 to find out the cause of interrupt
+	 * Note that IRQ is raised only when one of the SUPL, PROT,
+	 * MUTE and EARLY bits are set to logic 1.
+	 */
+	ret = tda8026_i2c_read(tda8026, TDA8026_REG0_ADDR, 1, &status);
+	if (ret < 0)
+		return IRQ_HANDLED;
+
+	if (slot < 3) {
+		/* slot 1 and slot 2 can be used for user card, it can raise
+		 * interrupt for card insert and remove.Other slot are for SAM
+		 * modules. They are either always present or alwyas absent
+		 */
+		if (status & TDA8026_REG0_PREL_MASK) {
+			if (status & TDA8026_REG0_PRES_MASK) {
+				dev_dbg(tda8026->dev, "card is inserted");
+				action |= SC_PHY_CARD_INSERTED;
+			} else {
+				dev_dbg(tda8026->dev, "card is removed");
+				action |= SC_PHY_CARD_REMOVED;
+			}
+		}
+	}
+
+	if (status & (TDA8026_REG0_EARLY_MASK | TDA8026_REG0_MUTE_MASK)) {
+		dev_dbg(tda8026->dev, "CARD EARLY INTERRUPT\n");
+		action |= SC_PHY_CARD_ATR_TIMEOUT;
+	}
+
+	if (status & TDA8026_REG0_PROT_MASK) {
+		dev_dbg(tda8026->dev, "CARD OVERHEAT/OVERLOAD INTERRUPT\n");
+		action |= SC_PHY_CARD_OVERHEAT;
+	}
+
+	if (action != 0x0 && tda8026->notify) {
+		/* add slot information. Pass slot-1 as for controller slot
+		 * starts from 0,1,2.. */
+		action |= ((slot-1) << SC_PHY_NOTIFICATION_SLOT_SHIFT);
+
+		/* notify action */
+		blocking_notifier_call_chain(&tda8026_notifier_list, action,
+					     phy_tda8026->notify_data);
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+ * PDWN bit is set/cleared to apply CLKPD[1:0] bit clock settings to
+ * the clock pin for the selected card slot.
+ */
+static int tda8026_pwdn(struct tda8026 *tda8026, u8 slot, int state)
+{
+	u8 reg0 = 0;
+	int ret = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+
+	if (state)
+		reg0 |= TDA8026_REG0_PDWN_MASK;
+	else
+		reg0 &= ~(TDA8026_REG0_PDWN_MASK);
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	return 0;
+}
+
+/*
+ * Set the card supply voltage.
+ * TDA PHY supports supply voltage of 1.8V, 3V and 5V.
+ */
+static int tda8026_set_voltage(struct tda8026 *tda8026, u8 slot, int voltage)
+{
+	u8 reg0 = 0;
+	int ret = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+
+	reg0 &= ~(TDA8026_REG0_VCC1V8_MASK | TDA8026_REG0_5V3VN_MASK);
+
+	switch (voltage) {
+	case SC_PHY_1_8V:
+		reg0 |= TDA8026_REG0_VCC1V8_MASK;
+	break;
+
+	case SC_PHY_5V:
+		reg0 |= TDA8026_REG0_5V3VN_MASK;
+	break;
+
+	case SC_PHY_3V:
+	default:
+	break;
+	}
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	return 0;
+}
+
+/*
+ * Enable the I/O line by setting I/OEN bit of slot's main address register.
+ * The I/O line should be enabled prior to card activation.
+ */
+static int tda8026_io_enable(struct tda8026 *tda8026, u8 slot)
+{
+	u8 reg0 = 0;
+	int ret = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 |= TDA8026_REG0_IOEN_MASK;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	return 0;
+}
+
+/*
+ * Disable the I/O line by clearing I/OEN bit of slot's main address register.
+ * The I/O line can be disabled post card activation.
+ */
+static int tda8026_io_disable(struct tda8026 *tda8026, u8 slot)
+{
+	u8 reg0 = 0;
+	int ret = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 &= ~(TDA8026_REG0_IOEN_MASK);
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	return 0;
+}
+
+/*
+ * Sets the mute counter in C[15:8] and C[7:0] register
+ * Write Reg[1:0] = 11 to select C[7:0] register
+ * Write Reg[1:0] = 10 to select C[15:8] register
+ */
+static int tda8026_set_atr_mute_time(struct tda8026 *tda8026, u8 slot,
+		int mute_counter)
+{
+	u8 reg0;
+	u8 mute_counter_high = (mute_counter & 0xFF00) >> 8;
+	u8 mute_counter_low = mute_counter & 0xFF;
+	int ret = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 |=  TDA8026_REG0_REG10_CLSB_VAL << TDA8026_REG0_REG10_SHIFT;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	/* Write the mute counter value in C[7:0] LSB register */
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG1_ADDR, 1,
+			&mute_counter_low);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 |=  TDA8026_REG0_REG10_CMSB_VAL << TDA8026_REG0_REG10_SHIFT;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	/* Write the mute counter value in C[15:8] MSB register */
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG1_ADDR, 1,
+			&mute_counter_high);
+	if (ret < 0)
+		return ret;
+	else
+		return 0;
+}
+
+/*
+ * Sets the ATR early time counter.
+ * Write Reg[1:0] = 01 to select D register
+ */
+static int tda8026_set_atr_early_time(struct tda8026 *tda8026, u8 slot,
+		int early_counter)
+{
+	u8 reg0;
+	int ret = 0;
+	u8 counter = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 |= TDA8026_REG0_REG10_D_VAL << TDA8026_REG0_REG10_SHIFT;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	/* Write the early atr counter value in D register */
+	counter = (early_counter - TDA8026_MIN_EARLY_CYCLE) & 0xFF;
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG1_ADDR, 1, &counter);
+	if (ret < 0)
+		return ret;
+	else
+		return 0;
+}
+
+static int tda8026_set_rstpin(struct tda8026 *tda8026, u8 slot, int state)
+{
+	u8 reg0 = 0;
+	u8 reg1 = 0;
+	int ret = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	/* Write Reg[1:0] = 00 to select CONFIG register */
+	reg0 = tda8026->reg0[slot];
+	reg0 &= ~TDA8026_REG0_REG10_MASK;
+	reg0 |= (TDA8026_REG0_REG10_CFG_VAL << TDA8026_REG0_REG10_SHIFT) &
+		TDA8026_REG0_REG10_MASK;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	/* Read the Reg1 value */
+	ret = tda8026_i2c_read(tda8026, TDA8026_REG1_ADDR, 1,
+			&reg1);
+	if (ret < 0)
+		return ret;
+
+	if (state)
+		reg1 |= TDA8026_REG1CFG_RSTIN_MASK;
+	else
+		reg1 &= ~TDA8026_REG1CFG_RSTIN_MASK;
+
+	/* Write the Reset value to the register  */
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG1_ADDR, 1, &reg1);
+	if (ret < 0)
+		return ret;
+	else
+		return 0;
+}
+
+/*
+ * Activate the card by setting START bit of slot's main register.
+ * Voltage selction and enabling of I/O lines should be done prior
+ * to activating the card.
+ */
+static int tda8026_activate_card(struct sc_phy *phy_tda8026, u8 slot)
+{
+	u8 reg0 = 0;
+	int ret = 0;
+	struct tda8026 *tda8026;
+
+	if (phy_tda8026 == NULL)
+		return -EINVAL;
+
+	tda8026	= (struct tda8026 *)phy_tda8026->pdata;
+
+	/* if PHY is used then RSTIN should be 1 for async cards,
+	 * currently this is applicable only for TDA8026
+	 */
+	tda8026_set_rstpin(tda8026, slot, 1);
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 |= TDA8026_REG0_START_MASK;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	return 0;
+}
+
+/*
+ * Deactivate the card by clearing the START bit of slot's main register
+ * We implement normal de-activation here.
+ * On clearing the START bit with normal deactivation, automatic
+ * deactivation is initiated and performaed by TDA8026.
+ */
+static int tda8026_deactivate_card(struct sc_phy *phy_tda8026, u8 slot)
+{
+	u8 reg0 = 0;
+	int ret = 0;
+
+	struct tda8026 *tda8026;
+
+	if (phy_tda8026 == NULL)
+		return -EINVAL;
+
+	tda8026	= (struct tda8026 *)phy_tda8026->pdata;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 &= ~(TDA8026_REG0_START_MASK);
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	return 0;
+}
+
+/*
+ * Warm reset is initiated by setting the WARM bit of slot's main register
+ */
+static int tda8026_warm_reset(struct sc_phy *phy_tda8026, u8 slot)
+{
+	u8 reg0 = 0;
+	int ret = 0;
+
+	struct tda8026 *tda8026;
+
+	if (phy_tda8026 == NULL)
+		return -EINVAL;
+
+	tda8026	= (struct tda8026 *)phy_tda8026->pdata;
+
+	/* See section 6.5 in TDA app note */
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 |= TDA8026_REG0_WARM_RESET_MASK;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	return 0;
+}
+
+/*
+ * Read and return the TDA8026 PHY product version
+ */
+static int tda8026_get_provider_version(struct tda8026 *tda8026)
+{
+	u8 val;
+	u8 version = 0;
+	int ret = 0;
+
+	/* Select Product version bank i.e Write CSB = 00 */
+	val = TDA8026_CSB_PV_INTSTAT_VAL;
+	ret = tda8026_i2c_write(tda8026, TDA8026_CSB_ADDR, 1, &val);
+	if (ret < 0)
+		return ret;
+
+	ret = tda8026_i2c_read(tda8026, TDA8026_PRODVER_ADDR, 1, &version);
+	if (ret < 0)
+		return ret;
+	else
+		dev_info(tda8026->dev, "Product Version = %x\n", version);
+
+	return version;
+}
+
+
+/*
+ * Set/reset the C4/C8 Pin
+ * Write Reg[1:0] = 00 to select CONFIG register
+ */
+static int tda8026_set_c4c8(struct tda8026 *tda8026, u8 slot, int state, int pin)
+{
+	u8 reg0 = 0;
+	u8 reg1 = 0;
+	int ret = 0;
+	int mask = 0;
+
+	if (slot != 0) {
+		/* C4/C8 pin value valid only for slot 1 */
+		return -EINVAL;
+	} else {
+		ret = tda8026_select_slot(tda8026, slot);
+		if (ret < 0)
+			return ret;
+
+		reg0 = tda8026->reg0[slot];
+		reg0 &= ~TDA8026_REG0_REG10_MASK;
+		reg0 |= (TDA8026_REG0_REG10_CFG_VAL <<
+				TDA8026_REG0_REG10_SHIFT) &
+			TDA8026_REG0_REG10_MASK;
+
+		ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+		if (ret < 0)
+			return ret;
+		else if (ret == 1)
+			tda8026->reg0[slot] = reg0;
+
+		ret = tda8026_i2c_read(tda8026, TDA8026_REG1_ADDR, 1,
+				&reg1);
+		if (ret < 0)
+			return ret;
+		if (pin == SC_PHY_PIN_C8)
+			mask = TDA8026_REG1CFG_C8_MASK;
+		else if (pin == SC_PHY_PIN_C4)
+			mask = TDA8026_REG1CFG_C4_MASK;
+		else
+			return -EINVAL;
+
+		if (state)
+			reg1 |= mask;
+		else
+			reg1 &= ~mask;
+
+		ret = tda8026_i2c_write(tda8026, TDA8026_REG1_ADDR, 1,
+				&reg1);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+/*
+ * Get the state of C4/C8 pin (high/low)
+ * Write Reg[1:0] = 00 to select CONFIG register
+ */
+static int tda8026_get_c4c8(struct tda8026 *tda8026, u8 slot, int pin)
+{
+	u8 reg0 = 0;
+	u8 reg1 = 0;
+	int ret = 0;
+	int mask = 0;
+
+	if (slot != 0) {
+		/* C4/C8 pin value valid only for slot 1 */
+		return -EINVAL;
+	} else {
+		ret = tda8026_select_slot(tda8026, slot);
+		if (ret < 0)
+			return ret;
+
+		reg0 = tda8026->reg0[slot];
+		reg0 &= ~TDA8026_REG0_REG10_MASK;
+		reg0 |= (TDA8026_REG0_REG10_CFG_VAL <<
+				TDA8026_REG0_REG10_SHIFT) &
+			TDA8026_REG0_REG10_MASK;
+
+		ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+		if (ret < 0)
+			return ret;
+		else if (ret == 1)
+			tda8026->reg0[slot] = reg0;
+
+		ret = tda8026_i2c_read(tda8026, TDA8026_REG1_ADDR, 1,
+				&reg1);
+		if (ret < 0)
+			return ret;
+
+		if (pin == SC_PHY_PIN_C8)
+			mask = TDA8026_REG1CFG_C8_MASK;
+		else if (pin == SC_PHY_PIN_C4)
+			mask = TDA8026_REG1CFG_C4_MASK;
+		else
+			return -EINVAL;
+
+		return (reg1 &= mask) ? 1 : 0;
+	}
+	return 0;
+}
+
+/*
+ * Set card clock
+ * Applicable only for synchronous mode
+ */
+static int tda8026_set_cardclk(struct tda8026 *tda8026, u8 slot,
+		int config)
+{
+	u8 reg0 = 0;
+	u8 reg1 = 0;
+	int ret = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 &= ~TDA8026_REG0_REG10_MASK;
+	reg0 |= (TDA8026_REG0_REG10_CFG_VAL << TDA8026_REG0_REG10_SHIFT) &
+		TDA8026_REG0_REG10_MASK;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	ret = tda8026_i2c_read(tda8026, TDA8026_REG1_ADDR, 1, &reg1);
+	if (ret < 0)
+		return ret;
+
+	reg1 &= ~(TDA8026_REG1CFG_CLKPD_MASK);
+	reg1 |= (config << TDA8026_REG1CFG_CLKPD_SHIFT) &
+		TDA8026_REG1CFG_CLKPD_MASK;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG1_ADDR, 1, &reg1);
+	if (ret < 0)
+		return ret;
+	else
+		return 0;
+}
+
+/*
+ * Set the CLKDIV[1:0] bits.
+ * CLKDIV[1:0] bits define the card clock frequency
+ * Write Reg[1:0] = 00 to select CONFIG register
+ */
+static int tda8026_set_clkdiv(struct tda8026 *tda8026, u8 slot, int div)
+{
+	u8 reg0 = 0;
+	u8 reg1 = 0;
+	int ret = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 &= ~TDA8026_REG0_REG10_MASK;
+	reg0 |= (TDA8026_REG0_REG10_CFG_VAL << TDA8026_REG0_REG10_SHIFT) &
+		TDA8026_REG0_REG10_MASK;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	ret = tda8026_i2c_read(tda8026, TDA8026_REG1_ADDR, 1, &reg1);
+	if (ret < 0)
+		return ret;
+
+	reg1 &= ~(TDA8026_REG1CFG_CLKDIV_MASK);
+	reg1 |= (div << TDA8026_REG1CFG_CLKDIV_SHIFT) &
+		TDA8026_REG1CFG_CLKDIV_MASK;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG1_ADDR, 1, &reg1);
+	if (ret < 0)
+		return ret;
+	else
+		return 0;
+}
+
+/*
+ * Get the CLKDIV[1:0] bits.
+ * CLKDIV[1:0] bits define the card clock frequency
+ * Write Reg[1:0] = 00 to select CONFIG register
+ */
+static int tda8026_get_clkdiv(struct tda8026 *tda8026, u8 slot)
+{
+	u8 reg0 = 0;
+	u8 reg1 = 0;
+	int ret = 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	reg0 = tda8026->reg0[slot];
+	reg0 &= ~TDA8026_REG0_REG10_MASK;
+	reg0 |= (TDA8026_REG0_REG10_CFG_VAL << TDA8026_REG0_REG10_SHIFT) &
+		TDA8026_REG0_REG10_MASK;
+
+	ret = tda8026_i2c_write(tda8026, TDA8026_REG0_ADDR, 1, &reg0);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		tda8026->reg0[slot] = reg0;
+
+	ret = tda8026_i2c_read(tda8026, TDA8026_REG1_ADDR, 1, &reg1);
+	if (ret < 0)
+		return ret;
+
+	ret = reg1 & TDA8026_REG1CFG_CLKDIV_MASK;
+
+	return ret;
+}
+
+/*
+ * Check if card is present in the slot.
+ */
+static int tda8026_card_present(struct tda8026 *tda8026, u8 slot)
+{
+	int present = 0;
+	int ret = 0;
+	u8 status = 0;
+
+	if (tda8026->enable == 0)
+		return 0;
+
+	ret = tda8026_select_slot(tda8026, slot);
+	if (ret < 0)
+		return ret;
+
+	ret = tda8026_i2c_read(tda8026, TDA8026_REG0_ADDR, 1, &status);
+	if (ret < 0)
+		return ret;
+
+	if (status & TDA8026_REG0_PRES_MASK)
+		present = 1;
+
+	return present;
+}
+
+/**
+ * tda8026_register_notify - register a notifier callback whenever a card
+ * event happens
+ * @nb: pointer to the notifier block for the callback events.
+ */
+static int tda8026_register_notify(struct sc_phy *phy_tda8026,
+		struct	notifier_block *nb, void *data)
+{
+	struct tda8026 *tda8026;
+
+	if (phy_tda8026 == NULL)
+		return -EINVAL;
+
+	phy_tda8026->notify_data = data;
+	tda8026	= (struct tda8026 *)phy_tda8026->pdata;
+	blocking_notifier_chain_register(&tda8026_notifier_list, nb);
+	tda8026->notify = 1;
+	return 0;
+}
+
+/**
+ * tda8026_unregister_notify - unregister a notifier callback
+ * event happens
+ * @nb: pointer to the notifier block for the callback events.
+ */
+static int tda8026_unregister_notify(struct sc_phy *phy_tda8026,
+		struct	notifier_block *nb)
+{
+	struct tda8026 *tda8026;
+
+	if (phy_tda8026 == NULL)
+		return -EINVAL;
+
+	tda8026	= (struct tda8026 *)phy_tda8026->pdata;
+	blocking_notifier_chain_unregister(&tda8026_notifier_list, nb);
+	tda8026->notify = 0;
+	return 0;
+}
+
+static int tda8026_set_config(struct sc_phy *phy_tda8026, u8 slot, enum
+		sc_phy_config attr, int value)
+{
+	int ret = 0;
+	struct tda8026 *tda8026;
+
+	if (phy_tda8026 == NULL)
+		return -EINVAL;
+
+	tda8026	= (struct tda8026 *)phy_tda8026->pdata;
+	switch (attr) {
+	case SC_PHY_CARD_SUPPLY_VOLTAGE:
+		ret = tda8026_set_voltage(tda8026, slot, value);
+		break;
+
+	case SC_PHY_ATR_MUTE_TIME:
+		ret = tda8026_set_atr_mute_time(tda8026, slot, value);
+		break;
+
+	case SC_PHY_ATR_EARLY_TIME:
+		ret = tda8026_set_atr_early_time(tda8026, slot, value);
+		break;
+
+	case SC_PHY_CARD_MODE:
+		if (value == SC_PHY_SYNC) {
+			/* set clkdiv to zero, rst pin to low and pwdn bit to
+			 * logic 0
+			 */
+			tda8026_set_clkdiv(tda8026, slot, 0);
+			tda8026_set_rstpin(tda8026, slot, 0);
+			tda8026_pwdn(tda8026, slot, 0);
+		} else {
+			/* Nothing to do, default mode is async */
+		}
+		break;
+
+	case SC_PHY_IO:
+		if (value)
+			ret = tda8026_io_enable(tda8026, slot);
+		else
+			ret = tda8026_io_disable(tda8026, slot);
+		break;
+
+	case SC_PHY_PIN_RST:
+		ret = tda8026_set_rstpin(tda8026, slot, value);
+		break;
+
+	case SC_PHY_CLKDIV:
+		ret = tda8026_set_clkdiv(tda8026, slot, value);
+		break;
+
+	case SC_PHY_MODE:
+		if (value == SC_PHY_ACTIVE)
+			tda8026_enable(tda8026);
+		else if (value == SC_PHY_SHUTDOWN)
+			tda8026_disable(tda8026);
+		else
+			ret = -EINVAL;
+		break;
+
+	case SC_PHY_PIN_C4:
+		ret = tda8026_set_c4c8(tda8026, slot, value, SC_PHY_PIN_C4);
+		break;
+
+	case SC_PHY_PIN_C8:
+		ret = tda8026_set_c4c8(tda8026, slot, value, SC_PHY_PIN_C8);
+		break;
+
+	case SC_PHY_PIN_CLK:
+		ret = tda8026_set_cardclk(tda8026, slot, value);
+		break;
+
+	default:
+		ret = -EINVAL;
+		dev_err(phy_tda8026->dev, "operation not supported:%d", attr);
+		break;
+	}
+	return ret;
+}
+
+static int tda8026_get_config(struct sc_phy *phy_tda8026, u8 slot, enum
+		sc_phy_config attr)
+{
+	int ret = -1;
+	struct tda8026 *tda8026;
+
+	if (phy_tda8026 == NULL)
+		return -EINVAL;
+
+	tda8026	= (struct tda8026 *)phy_tda8026->pdata;
+	switch (attr) {
+	case SC_PHY_CARD_PRESENCE:
+		ret = tda8026_card_present(tda8026, slot);
+	break;
+
+	case SC_PHY_VERSION:
+		ret = tda8026_get_provider_version(tda8026);
+	break;
+
+	case SC_PHY_CLKDIV:
+		ret = tda8026_get_clkdiv(tda8026, slot);
+	break;
+
+	case SC_PHY_PIN_C4:
+		ret = tda8026_get_c4c8(tda8026, slot, SC_PHY_PIN_C4);
+	break;
+
+	case SC_PHY_PIN_C8:
+		ret = tda8026_get_c4c8(tda8026, slot, SC_PHY_PIN_C8);
+	break;
+
+	default:
+		ret = -EINVAL;
+		dev_err(phy_tda8026->dev, "operation not supported:%d", attr);
+	break;
+	}
+	return ret;
+}
+
+#if defined(CONFIG_OF)
+static const struct of_device_id tda8026_id_table[];
+#endif
+
+static int tda8026_parse_dt(struct device *dev, struct tda8026 *pdata)
+{
+	struct device_node *np = dev->of_node;
+	const struct of_device_id *match;
+	int ret = 0;
+
+	match = of_match_device(of_match_ptr(tda8026_id_table), dev);
+	if (!match)
+		return -EINVAL;
+
+	pdata->shutdown_gpio = of_get_named_gpio(np, "shutdown-gpio", 0);
+	if (!gpio_is_valid(pdata->shutdown_gpio)) {
+		dev_err(dev, "Failed to get shutdown gpio\n");
+		return -EINVAL;
+	}
+
+	ret = devm_gpio_request_one(dev, pdata->shutdown_gpio,
+			GPIOF_DIR_OUT, "shutdown_gpio");
+	if (ret) {
+		dev_err(dev, "Failed to request shutdown_gpio\n");
+		return ret;
+	}
+	return 0;
+}
+
+static int tda8026_i2c_probe(struct i2c_client *i2c,
+		const struct i2c_device_id *id)
+{
+	int ret = 0;
+	int irq_gpio = 0;
+	struct sc_phy *phy_tda8026;
+	struct tda8026 *pdata;
+
+	struct device *dev = &i2c->dev;
+	struct device_node *np = dev->of_node;
+
+	pdata = devm_kzalloc(dev, sizeof(struct tda8026), GFP_KERNEL);
+	if (pdata == NULL)
+		return -ENOMEM;
+
+	phy_tda8026 = devm_kzalloc(dev, sizeof(struct sc_phy), GFP_KERNEL);
+	if (phy_tda8026 == NULL)
+		return -ENOMEM;
+
+	ret = tda8026_parse_dt(dev, pdata);
+	if (ret != 0)
+		return ret;
+
+	i2c_set_clientdata(i2c, phy_tda8026);
+	phy_tda8026->dev = &i2c->dev;
+
+	phy_tda8026->pdata = (void *)pdata;
+	pdata->control_data = i2c;
+	pdata->irq = i2c->irq;
+	pdata->dev = phy_tda8026->dev;
+	pdata->notify = 0;
+
+	if (pdata->irq == 0) {
+		/* look for the field irq-gpio in DT */
+		irq_gpio = of_get_named_gpio(np, "irq-gpio", 0);
+		if (!gpio_is_valid(irq_gpio)) {
+			dev_err(dev, "Failed to get irq gpio,\n");
+			return -EIO;
+		}
+		pdata->irq = gpio_to_irq(irq_gpio);
+		ret = devm_gpio_request_one(dev, irq_gpio,
+				GPIOF_DIR_IN, "irq_gpio");
+		if (ret) {
+			dev_err(dev, "Failed to request irq_gpio\n");
+			return ret;
+		}
+	}
+
+	ret = devm_request_threaded_irq(dev, pdata->irq, NULL, tda8026_irq,
+			IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+			"tda8026", phy_tda8026);
+	if (ret < 0) {
+		dev_err(phy_tda8026->dev, "can't get irq %d err %d\n",
+			pdata->irq, ret);
+		return ret;
+	}
+
+	/* enable phy */
+	tda8026_enable(pdata);
+
+	tda8026_clear_interrupts(pdata);
+	phy_tda8026->set_config	= tda8026_set_config;
+	phy_tda8026->get_config	= tda8026_get_config;
+	phy_tda8026->activate_card	= tda8026_activate_card;
+	phy_tda8026->deactivate_card	= tda8026_deactivate_card;
+	phy_tda8026->warm_reset	= tda8026_warm_reset;
+	phy_tda8026->register_notify	= tda8026_register_notify;
+	phy_tda8026->unregister_notify	= tda8026_unregister_notify;
+
+	/* disable phy */
+	tda8026_disable(pdata);
+
+	return 0;
+}
+
+static int tda8026_i2c_remove(struct i2c_client *i2c)
+{
+	struct sc_phy *phy_tda8026;
+	struct tda8026 *tda8026;
+	int action = 0;
+
+	phy_tda8026 = i2c_get_clientdata(i2c);
+	if (phy_tda8026 == NULL)
+		return -EINVAL;
+
+	tda8026	= (struct tda8026 *)phy_tda8026->pdata;
+
+	/* notify action */
+	action = SC_PHY_REMOVED;
+	blocking_notifier_call_chain(&tda8026_notifier_list, action,
+				     phy_tda8026->notify_data);
+	tda8026->notify = 0;
+
+	/* enable shutdown mode */
+	tda8026_disable(tda8026);
+
+	return 0;
+}
+
+#if defined(CONFIG_OF)
+static const struct of_device_id tda8026_id_table[] = {
+	{ .compatible = "nxp,tda8026" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, tda8026_id_table);
+#endif
+
+static const struct i2c_device_id tda8026_i2c_id[] = {
+	{"tda8026", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tda8026_i2c_id);
+
+static struct i2c_driver tda8026_i2c_driver = {
+	.driver = {
+		.name = "tda8026",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(tda8026_id_table),
+	},
+	.probe = tda8026_i2c_probe,
+	.remove = tda8026_i2c_remove,
+	.id_table = tda8026_i2c_id,
+};
+static int __init tda8026_i2c_init(void)
+{
+	int ret;
+	ret = i2c_add_driver(&tda8026_i2c_driver);
+	if (ret != 0)
+		pr_err("Failed to register TDA8026 I2C driver: %d\n", ret);
+	return ret;
+}
+/* init early so consumer devices can complete system boot */
+subsys_initcall(tda8026_i2c_init);
+
+static void __exit tda8026_i2c_exit(void)
+{
+	i2c_del_driver(&tda8026_i2c_driver);
+}
+module_exit(tda8026_i2c_exit);
+
+MODULE_AUTHOR("Maulik Mankad <maulik@...com>");
+MODULE_DESCRIPTION("TDA8026 Smart Card NXP PHY driver");
+MODULE_LICENSE("GPL");
-- 
1.7.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ