summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2014-06-04 21:04:28 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2014-06-04 21:04:28 +0200
commitad9f56888f00afca589e3a9e14ff856cebf540a7 (patch)
treeb12b889ef1ce04141d3fe3520706ad29fb15668f /drivers
parentc9bbafe1dc98a7264f756e84c3c32eb5bebe0218 (diff)
parent7b8f46983e2bc00952f2e2afb9dec80cbb266621 (diff)
downloadbarebox-ad9f56888f00afca589e3a9e14ff856cebf540a7.tar.gz
barebox-ad9f56888f00afca589e3a9e14ff856cebf540a7.tar.xz
Merge branch 'for-next/tegra'
Diffstat (limited to 'drivers')
-rw-r--r--drivers/Kconfig1
-rw-r--r--drivers/Makefile1
-rw-r--r--drivers/clk/tegra/clk-periph.c46
-rw-r--r--drivers/clk/tegra/clk-tegra20.c16
-rw-r--r--drivers/clk/tegra/clk-tegra30.c19
-rw-r--r--drivers/clk/tegra/clk.c145
-rw-r--r--drivers/clk/tegra/clk.h18
-rw-r--r--drivers/i2c/busses/Kconfig4
-rw-r--r--drivers/i2c/busses/Makefile1
-rw-r--r--drivers/i2c/busses/i2c-tegra.c699
-rw-r--r--drivers/mci/mci-core.c5
-rw-r--r--drivers/mci/tegra-sdmmc.c37
-rw-r--r--drivers/pinctrl/pinctrl-tegra30.c159
-rw-r--r--drivers/reset/Kconfig13
-rw-r--r--drivers/reset/Makefile1
-rw-r--r--drivers/reset/core.c236
16 files changed, 1371 insertions, 30 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 7a2aa2810c..53e1e97560 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -26,5 +26,6 @@ source "drivers/w1/Kconfig"
source "drivers/pinctrl/Kconfig"
source "drivers/bus/Kconfig"
source "drivers/regulator/Kconfig"
+source "drivers/reset/Kconfig"
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index f79da89e94..ef3604f56c 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -25,3 +25,4 @@ obj-$(CONFIG_W1) += w1/
obj-y += pinctrl/
obj-y += bus/
obj-$(CONFIG_REGULATOR) += regulator/
+obj-$(CONFIG_RESET_CONTROLLER) += reset/
diff --git a/drivers/clk/tegra/clk-periph.c b/drivers/clk/tegra/clk-periph.c
index be83955db1..e4e5412b09 100644
--- a/drivers/clk/tegra/clk-periph.c
+++ b/drivers/clk/tegra/clk-periph.c
@@ -74,33 +74,15 @@ static int clk_periph_is_enabled(struct clk *hw)
static int clk_periph_enable(struct clk *hw)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
- u32 reg;
-
- reg = readl(periph->rst_reg);
- reg |= (1 << periph->rst_shift);
- writel(reg, periph->rst_reg);
periph->gate->ops->enable(periph->gate);
- udelay(2);
-
- reg = readl(periph->rst_reg);
- reg &= ~(1 << periph->rst_shift);
- writel(reg, periph->rst_reg);
-
return 0;
}
static void clk_periph_disable(struct clk *hw)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
- u32 reg;
-
- reg = readl(periph->rst_reg);
- reg |= (1 << periph->rst_shift);
- writel(reg, periph->rst_reg);
-
- udelay(2);
periph->gate->ops->disable(periph->gate);
}
@@ -124,10 +106,10 @@ const struct clk_ops tegra_clk_periph_nodiv_ops = {
.disable = clk_periph_disable,
};
-struct clk *_tegra_clk_register_periph(const char *name,
+static struct clk *_tegra_clk_register_periph(const char *name,
const char **parent_names, int num_parents,
void __iomem *clk_base, u32 reg_offset, u8 id, u8 flags,
- bool has_div)
+ int div)
{
struct tegra_clk_periph *periph;
int ret, gate_offs, rst_offs;
@@ -154,15 +136,20 @@ struct clk *_tegra_clk_register_periph(const char *name,
if (!periph->gate)
goto out_gate;
- if (has_div) {
+ if (div == 8) {
+ periph->div = tegra_clk_divider_alloc(NULL, NULL, clk_base +
+ reg_offset, 0, TEGRA_DIVIDER_ROUND_UP, 0, 8, 1);
+ if (!periph->div)
+ goto out_div;
+ } else if (div == 16) {
periph->div = tegra_clk_divider_alloc(NULL, NULL, clk_base +
- reg_offset, 0, TEGRA_DIVIDER_ROUND_UP, 0, 8, 1);
+ reg_offset, 0, TEGRA_DIVIDER_ROUND_UP, 0, 16, 0);
if (!periph->div)
goto out_div;
}
periph->hw.name = name;
- periph->hw.ops = has_div ? &tegra_clk_periph_ops :
+ periph->hw.ops = div ? &tegra_clk_periph_ops :
&tegra_clk_periph_nodiv_ops;
periph->hw.parent_names = parent_names;
periph->hw.num_parents = num_parents;
@@ -199,7 +186,7 @@ struct clk *tegra_clk_register_periph_nodiv(const char *name,
{
return _tegra_clk_register_periph(name, parent_names, num_parents,
clk_base, reg_offset, id, flags,
- false);
+ 0);
}
struct clk *tegra_clk_register_periph(const char *name,
@@ -208,5 +195,14 @@ struct clk *tegra_clk_register_periph(const char *name,
{
return _tegra_clk_register_periph(name, parent_names, num_parents,
clk_base, reg_offset, id, flags,
- true);
+ 8);
+}
+
+struct clk *tegra_clk_register_periph_div16(const char *name,
+ const char **parent_names, int num_parents,
+ void __iomem *clk_base, u32 reg_offset, u8 id, u8 flags)
+{
+ return _tegra_clk_register_periph(name, parent_names, num_parents,
+ clk_base, reg_offset, id, flags,
+ 16);
}
diff --git a/drivers/clk/tegra/clk-tegra20.c b/drivers/clk/tegra/clk-tegra20.c
index ea39f46b15..5803414b93 100644
--- a/drivers/clk/tegra/clk-tegra20.c
+++ b/drivers/clk/tegra/clk-tegra20.c
@@ -301,6 +301,19 @@ static void tegra20_periph_init(void)
clks[TEGRA20_CLK_SDMMC4] = tegra_clk_register_periph("sdmmc4",
mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
CRC_CLK_SOURCE_SDMMC4, TEGRA20_CLK_SDMMC4, 1);
+
+ clks[TEGRA20_CLK_I2C1] = tegra_clk_register_periph_div16("i2c1",
+ mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
+ CRC_CLK_SOURCE_I2C1, TEGRA20_CLK_I2C1, 1);
+ clks[TEGRA20_CLK_I2C2] = tegra_clk_register_periph_div16("i2c2",
+ mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
+ CRC_CLK_SOURCE_I2C2, TEGRA20_CLK_I2C2, 1);
+ clks[TEGRA20_CLK_I2C3] = tegra_clk_register_periph_div16("i2c3",
+ mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
+ CRC_CLK_SOURCE_I2C3, TEGRA20_CLK_I2C3, 1);
+ clks[TEGRA20_CLK_DVC] = tegra_clk_register_periph_div16("dvc",
+ mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
+ CRC_CLK_SOURCE_DVC, TEGRA20_CLK_DVC, 1);
}
static struct tegra_clk_init_table init_table[] = {
@@ -347,6 +360,9 @@ static int tegra20_car_probe(struct device_d *dev)
of_clk_add_provider(dev->device_node, of_clk_src_onecell_get,
&clk_data);
+ tegra_clk_init_rst_controller(car_base, dev->device_node, 3 * 32);
+ tegra_clk_reset_uarts();
+
return 0;
}
diff --git a/drivers/clk/tegra/clk-tegra30.c b/drivers/clk/tegra/clk-tegra30.c
index 94bbeace4a..3b508304c5 100644
--- a/drivers/clk/tegra/clk-tegra30.c
+++ b/drivers/clk/tegra/clk-tegra30.c
@@ -296,6 +296,22 @@ static void tegra30_periph_init(void)
clks[TEGRA30_CLK_SDMMC4] = tegra_clk_register_periph("sdmmc4",
mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
CRC_CLK_SOURCE_SDMMC4, TEGRA30_CLK_SDMMC4, 1);
+
+ clks[TEGRA30_CLK_I2C1] = tegra_clk_register_periph_div16("i2c1",
+ mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
+ CRC_CLK_SOURCE_I2C1, TEGRA30_CLK_I2C1, 1);
+ clks[TEGRA30_CLK_I2C2] = tegra_clk_register_periph_div16("i2c2",
+ mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
+ CRC_CLK_SOURCE_I2C2, TEGRA30_CLK_I2C2, 1);
+ clks[TEGRA30_CLK_I2C3] = tegra_clk_register_periph_div16("i2c3",
+ mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
+ CRC_CLK_SOURCE_I2C3, TEGRA30_CLK_I2C3, 1);
+ clks[TEGRA30_CLK_I2C4] = tegra_clk_register_periph_div16("i2c4",
+ mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
+ CRC_CLK_SOURCE_I2C4, TEGRA30_CLK_I2C4, 1);
+ clks[TEGRA30_CLK_I2C5] = tegra_clk_register_periph_div16("i2c5",
+ mux_pllpcm_clkm, ARRAY_SIZE(mux_pllpcm_clkm), car_base,
+ CRC_CLK_SOURCE_DVC, TEGRA30_CLK_I2C5, 1);
}
static struct tegra_clk_init_table init_table[] = {
@@ -341,6 +357,9 @@ static int tegra30_car_probe(struct device_d *dev)
of_clk_add_provider(dev->device_node, of_clk_src_onecell_get,
&clk_data);
+ tegra_clk_init_rst_controller(car_base, dev->device_node, 6 * 32);
+ tegra_clk_reset_uarts();
+
return 0;
}
diff --git a/drivers/clk/tegra/clk.c b/drivers/clk/tegra/clk.c
index f4013e7e79..cb4d920203 100644
--- a/drivers/clk/tegra/clk.c
+++ b/drivers/clk/tegra/clk.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Lucas Stach <l.stach@pengutronix.de>
+ * Copyright (C) 2013-2014 Lucas Stach <l.stach@pengutronix.de>
*
* Based on the Linux Tegra clock code
*
@@ -18,9 +18,103 @@
#include <common.h>
#include <linux/clk.h>
+#include <linux/reset-controller.h>
+#include <mach/lowlevel.h>
#include "clk.h"
+#define CLK_OUT_ENB_L 0x010
+#define CLK_OUT_ENB_H 0x014
+#define CLK_OUT_ENB_U 0x018
+#define CLK_OUT_ENB_V 0x360
+#define CLK_OUT_ENB_W 0x364
+#define CLK_OUT_ENB_X 0x280
+#define CLK_OUT_ENB_SET_L 0x320
+#define CLK_OUT_ENB_CLR_L 0x324
+#define CLK_OUT_ENB_SET_H 0x328
+#define CLK_OUT_ENB_CLR_H 0x32c
+#define CLK_OUT_ENB_SET_U 0x330
+#define CLK_OUT_ENB_CLR_U 0x334
+#define CLK_OUT_ENB_SET_V 0x440
+#define CLK_OUT_ENB_CLR_V 0x444
+#define CLK_OUT_ENB_SET_W 0x448
+#define CLK_OUT_ENB_CLR_W 0x44c
+#define CLK_OUT_ENB_SET_X 0x284
+#define CLK_OUT_ENB_CLR_X 0x288
+
+#define RST_DEVICES_L 0x004
+#define RST_DEVICES_H 0x008
+#define RST_DEVICES_U 0x00C
+#define RST_DFLL_DVCO 0x2F4
+#define RST_DEVICES_V 0x358
+#define RST_DEVICES_W 0x35C
+#define RST_DEVICES_X 0x28C
+#define RST_DEVICES_SET_L 0x300
+#define RST_DEVICES_CLR_L 0x304
+#define RST_DEVICES_SET_H 0x308
+#define RST_DEVICES_CLR_H 0x30c
+#define RST_DEVICES_SET_U 0x310
+#define RST_DEVICES_CLR_U 0x314
+#define RST_DEVICES_SET_V 0x430
+#define RST_DEVICES_CLR_V 0x434
+#define RST_DEVICES_SET_W 0x438
+#define RST_DEVICES_CLR_W 0x43c
+#define RST_DEVICES_SET_X 0x290
+#define RST_DEVICES_CLR_X 0x294
+
+static struct tegra_clk_periph_regs periph_regs[] = {
+ [0] = {
+ .enb_reg = CLK_OUT_ENB_L,
+ .enb_set_reg = CLK_OUT_ENB_SET_L,
+ .enb_clr_reg = CLK_OUT_ENB_CLR_L,
+ .rst_reg = RST_DEVICES_L,
+ .rst_set_reg = RST_DEVICES_SET_L,
+ .rst_clr_reg = RST_DEVICES_CLR_L,
+ },
+ [1] = {
+ .enb_reg = CLK_OUT_ENB_H,
+ .enb_set_reg = CLK_OUT_ENB_SET_H,
+ .enb_clr_reg = CLK_OUT_ENB_CLR_H,
+ .rst_reg = RST_DEVICES_H,
+ .rst_set_reg = RST_DEVICES_SET_H,
+ .rst_clr_reg = RST_DEVICES_CLR_H,
+ },
+ [2] = {
+ .enb_reg = CLK_OUT_ENB_U,
+ .enb_set_reg = CLK_OUT_ENB_SET_U,
+ .enb_clr_reg = CLK_OUT_ENB_CLR_U,
+ .rst_reg = RST_DEVICES_U,
+ .rst_set_reg = RST_DEVICES_SET_U,
+ .rst_clr_reg = RST_DEVICES_CLR_U,
+ },
+ [3] = {
+ .enb_reg = CLK_OUT_ENB_V,
+ .enb_set_reg = CLK_OUT_ENB_SET_V,
+ .enb_clr_reg = CLK_OUT_ENB_CLR_V,
+ .rst_reg = RST_DEVICES_V,
+ .rst_set_reg = RST_DEVICES_SET_V,
+ .rst_clr_reg = RST_DEVICES_CLR_V,
+ },
+ [4] = {
+ .enb_reg = CLK_OUT_ENB_W,
+ .enb_set_reg = CLK_OUT_ENB_SET_W,
+ .enb_clr_reg = CLK_OUT_ENB_CLR_W,
+ .rst_reg = RST_DEVICES_W,
+ .rst_set_reg = RST_DEVICES_SET_W,
+ .rst_clr_reg = RST_DEVICES_CLR_W,
+ },
+ [5] = {
+ .enb_reg = CLK_OUT_ENB_X,
+ .enb_set_reg = CLK_OUT_ENB_SET_X,
+ .enb_clr_reg = CLK_OUT_ENB_CLR_X,
+ .rst_reg = RST_DEVICES_X,
+ .rst_set_reg = RST_DEVICES_SET_X,
+ .rst_clr_reg = RST_DEVICES_CLR_X,
+ },
+};
+
+static void __iomem *car_base;
+
void tegra_init_from_table(struct tegra_clk_init_table *tbl,
struct clk *clks[], int clk_max)
{
@@ -55,3 +149,52 @@ void tegra_init_from_table(struct tegra_clk_init_table *tbl,
}
}
}
+
+static int tegra_clk_rst_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ tegra_read_chipid();
+
+ writel(BIT(id % 32), car_base + periph_regs[id / 32].rst_set_reg);
+
+ return 0;
+}
+
+static int tegra_clk_rst_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ writel(BIT(id % 32), car_base + periph_regs[id / 32].rst_clr_reg);
+
+ return 0;
+}
+
+static struct reset_control_ops rst_ops = {
+ .assert = tegra_clk_rst_assert,
+ .deassert = tegra_clk_rst_deassert,
+};
+
+static struct reset_controller_dev rst_ctlr = {
+ .ops = &rst_ops,
+ .of_reset_n_cells = 1,
+};
+
+void tegra_clk_init_rst_controller(void __iomem *base, struct device_node *np,
+ unsigned int num)
+{
+ car_base = base;
+
+ rst_ctlr.of_node = np;
+ rst_ctlr.nr_resets = num;
+ reset_controller_register(&rst_ctlr);
+}
+
+void tegra_clk_reset_uarts(void) {
+ int i;
+ int console_device_ids[] = {6, 7, 55, 65, 66};
+
+ for (i = 0; i < ARRAY_SIZE(console_device_ids); i++) {
+ rst_ops.assert(&rst_ctlr, console_device_ids[i]);
+ udelay(2);
+ rst_ops.deassert(&rst_ctlr, console_device_ids[i]);
+ }
+};
diff --git a/drivers/clk/tegra/clk.h b/drivers/clk/tegra/clk.h
index 9bb8f1c457..d5d0730602 100644
--- a/drivers/clk/tegra/clk.h
+++ b/drivers/clk/tegra/clk.h
@@ -138,6 +138,10 @@ struct clk *tegra_clk_register_periph(const char *name,
const char **parent_names, int num_parents,
void __iomem *clk_base, u32 reg_offset, u8 id, u8 flags);
+struct clk *tegra_clk_register_periph_div16(const char *name,
+ const char **parent_names, int num_parents,
+ void __iomem *clk_base, u32 reg_offset, u8 id, u8 flags);
+
/* struct clk_init_table - clock initialization table */
struct tegra_clk_init_table {
unsigned int clk_id;
@@ -148,3 +152,17 @@ struct tegra_clk_init_table {
void tegra_init_from_table(struct tegra_clk_init_table *tbl,
struct clk *clks[], int clk_max);
+
+struct tegra_clk_periph_regs {
+ u32 enb_reg;
+ u32 enb_set_reg;
+ u32 enb_clr_reg;
+ u32 rst_reg;
+ u32 rst_set_reg;
+ u32 rst_clr_reg;
+};
+
+void tegra_clk_init_rst_controller(void __iomem *base, struct device_node *np,
+ unsigned int num);
+
+void tegra_clk_reset_uarts(void);
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 68d9b469e8..370abb0c2e 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -20,6 +20,10 @@ config I2C_OMAP
bool "OMAP I2C Master driver"
depends on ARCH_OMAP
+config I2C_TEGRA
+ bool "Tegra I2C master driver"
+ depends on ARCH_TEGRA
+
config I2C_VERSATILE
tristate "ARM Versatile/Realview I2C bus support"
depends on ARCH_VERSATILE
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index a30f9b87e3..9823d1bd36 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -1,4 +1,5 @@
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_IMX) += i2c-imx.o
obj-$(CONFIG_I2C_OMAP) += i2c-omap.o
+obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o
obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o
diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
new file mode 100644
index 0000000000..c52da18b57
--- /dev/null
+++ b/drivers/i2c/busses/i2c-tegra.c
@@ -0,0 +1,699 @@
+/*
+ * Copyright (C) 2014 Lucas Stach <l.stach@pengutronix.de>
+ *
+ * Partly based on code Copyright (C) 2010 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+#include <common.h>
+#include <clock.h>
+#include <init.h>
+#include <io.h>
+#include <malloc.h>
+#include <i2c/i2c.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/reset.h>
+
+#include <asm/unaligned.h>
+
+#define TEGRA_I2C_TIMEOUT (msecs_to_jiffies(1000))
+#define BYTES_PER_FIFO_WORD 4
+
+#define I2C_CNFG 0x000
+#define I2C_CNFG_DEBOUNCE_CNT_SHIFT 12
+#define I2C_CNFG_PACKET_MODE_EN (1<<10)
+#define I2C_CNFG_NEW_MASTER_FSM (1<<11)
+#define I2C_STATUS 0x01C
+#define I2C_SL_CNFG 0x020
+#define I2C_SL_CNFG_NACK (1<<1)
+#define I2C_SL_CNFG_NEWSL (1<<2)
+#define I2C_SL_ADDR1 0x02c
+#define I2C_SL_ADDR2 0x030
+#define I2C_TX_FIFO 0x050
+#define I2C_RX_FIFO 0x054
+#define I2C_PACKET_TRANSFER_STATUS 0x058
+#define I2C_FIFO_CONTROL 0x05c
+#define I2C_FIFO_CONTROL_TX_FLUSH (1<<1)
+#define I2C_FIFO_CONTROL_RX_FLUSH (1<<0)
+#define I2C_FIFO_CONTROL_TX_TRIG_SHIFT 5
+#define I2C_FIFO_CONTROL_RX_TRIG_SHIFT 2
+#define I2C_FIFO_STATUS 0x060
+#define I2C_FIFO_STATUS_TX_MASK 0xF0
+#define I2C_FIFO_STATUS_TX_SHIFT 4
+#define I2C_FIFO_STATUS_RX_MASK 0x0F
+#define I2C_FIFO_STATUS_RX_SHIFT 0
+#define I2C_INT_MASK 0x064
+#define I2C_INT_STATUS 0x068
+#define I2C_INT_PACKET_XFER_COMPLETE (1<<7)
+#define I2C_INT_ALL_PACKETS_XFER_COMPLETE (1<<6)
+#define I2C_INT_TX_FIFO_OVERFLOW (1<<5)
+#define I2C_INT_RX_FIFO_UNDERFLOW (1<<4)
+#define I2C_INT_NO_ACK (1<<3)
+#define I2C_INT_ARBITRATION_LOST (1<<2)
+#define I2C_INT_TX_FIFO_DATA_REQ (1<<1)
+#define I2C_INT_RX_FIFO_DATA_REQ (1<<0)
+#define I2C_CLK_DIVISOR 0x06c
+#define I2C_CLK_DIVISOR_STD_FAST_MODE_SHIFT 16
+#define I2C_CLK_MULTIPLIER_STD_FAST_MODE 8
+
+#define DVC_CTRL_REG1 0x000
+#define DVC_CTRL_REG1_INTR_EN (1<<10)
+#define DVC_CTRL_REG2 0x004
+#define DVC_CTRL_REG3 0x008
+#define DVC_CTRL_REG3_SW_PROG (1<<26)
+#define DVC_CTRL_REG3_I2C_DONE_INTR_EN (1<<30)
+#define DVC_STATUS 0x00c
+#define DVC_STATUS_I2C_DONE_INTR (1<<30)
+
+#define I2C_ERR_NONE 0x00
+#define I2C_ERR_NO_ACK 0x01
+#define I2C_ERR_ARBITRATION_LOST 0x02
+#define I2C_ERR_UNKNOWN_INTERRUPT 0x04
+
+#define PACKET_HEADER0_HEADER_SIZE_SHIFT 28
+#define PACKET_HEADER0_PACKET_ID_SHIFT 16
+#define PACKET_HEADER0_CONT_ID_SHIFT 12
+#define PACKET_HEADER0_PROTOCOL_I2C (1<<4)
+
+#define I2C_HEADER_HIGHSPEED_MODE (1<<22)
+#define I2C_HEADER_CONT_ON_NAK (1<<21)
+#define I2C_HEADER_SEND_START_BYTE (1<<20)
+#define I2C_HEADER_READ (1<<19)
+#define I2C_HEADER_10BIT_ADDR (1<<18)
+#define I2C_HEADER_IE_ENABLE (1<<17)
+#define I2C_HEADER_REPEAT_START (1<<16)
+#define I2C_HEADER_CONTINUE_XFER (1<<15)
+#define I2C_HEADER_MASTER_ADDR_SHIFT 12
+#define I2C_HEADER_SLAVE_ADDR_SHIFT 1
+/*
+ * msg_end_type: The bus control which need to be send at end of transfer.
+ * @MSG_END_STOP: Send stop pulse at end of transfer.
+ * @MSG_END_REPEAT_START: Send repeat start at end of transfer.
+ * @MSG_END_CONTINUE: The following on message is coming and so do not send
+ * stop or repeat start.
+ */
+enum msg_end_type {
+ MSG_END_STOP,
+ MSG_END_REPEAT_START,
+ MSG_END_CONTINUE,
+};
+
+/**
+ * struct tegra_i2c_hw_feature : Different HW support on Tegra
+ * @has_continue_xfer_support: Continue transfer supports.
+ * @has_per_pkt_xfer_complete_irq: Has enable/disable capability for transfer
+ * complete interrupt per packet basis.
+ * @has_single_clk_source: The i2c controller has single clock source. Tegra30
+ * and earlier Socs has two clock sources i.e. div-clk and
+ * fast-clk.
+ * @clk_divisor_hs_mode: Clock divisor in HS mode.
+ * @clk_divisor_std_fast_mode: Clock divisor in standard/fast mode. It is
+ * applicable if there is no fast clock source i.e. single clock
+ * source.
+ */
+
+struct tegra_i2c_hw_feature {
+ bool has_continue_xfer_support;
+ bool has_per_pkt_xfer_complete_irq;
+ bool has_single_clk_source;
+ int clk_divisor_hs_mode;
+ int clk_divisor_std_fast_mode;
+};
+
+/**
+ * struct tegra_i2c_dev - per device i2c context
+ * @dev: device reference for power management
+ * @hw: Tegra i2c hw feature.
+ * @adapter: core i2c layer adapter information
+ * @div_clk: clock reference for div clock of i2c controller.
+ * @fast_clk: clock reference for fast clock of i2c controller.
+ * @base: ioremapped registers cookie
+ * @is_dvc: identifies the DVC i2c controller, has a different register layout
+ * @msg_err: error code for completed message
+ * @msg_buf: pointer to current message data
+ * @msg_buf_remaining: size of unsent data in the message buffer
+ * @msg_read: identifies read transfers
+ * @bus_clk_rate: current i2c bus clock rate
+ */
+struct tegra_i2c_dev {
+ struct device_d *dev;
+ const struct tegra_i2c_hw_feature *hw;
+ struct i2c_adapter adapter;
+ struct clk *div_clk;
+ struct clk *fast_clk;
+ struct reset_control *rst;
+ void __iomem *base;
+ int is_dvc;
+ int msg_err;
+ u8 *msg_buf;
+ size_t msg_buf_remaining;
+ int msg_read;
+ u32 bus_clk_rate;
+};
+#define to_tegra_i2c_dev(a) container_of(a, struct tegra_i2c_dev, adapter)
+
+static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned long reg)
+{
+ writel(val, i2c_dev->base + reg);
+}
+
+static u32 dvc_readl(struct tegra_i2c_dev *i2c_dev, unsigned long reg)
+{
+ return readl(i2c_dev->base + reg);
+}
+
+/*
+ * i2c_writel and i2c_readl will offset the register if necessary to talk
+ * to the I2C block inside the DVC block
+ */
+static unsigned long tegra_i2c_reg_addr(struct tegra_i2c_dev *i2c_dev,
+ unsigned long reg)
+{
+ if (i2c_dev->is_dvc)
+ reg += (reg >= I2C_TX_FIFO) ? 0x10 : 0x40;
+ return reg;
+}
+
+static void i2c_writel(struct tegra_i2c_dev *i2c_dev, u32 val,
+ unsigned long reg)
+{
+ writel(val, i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg));
+
+ /* Read back register to make sure that register writes completed */
+ if (reg != I2C_TX_FIFO)
+ readl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg));
+}
+
+static u32 i2c_readl(struct tegra_i2c_dev *i2c_dev, unsigned long reg)
+{
+ return readl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg));
+}
+
+static void i2c_writesl(struct tegra_i2c_dev *i2c_dev, void *data,
+ unsigned long reg, int len)
+{
+ writesl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg), data, len);
+}
+
+static void i2c_readsl(struct tegra_i2c_dev *i2c_dev, void *data,
+ unsigned long reg, int len)
+{
+ readsl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg), data, len);
+}
+
+static int tegra_i2c_flush_fifos(struct tegra_i2c_dev *i2c_dev)
+{
+ uint64_t start = get_time_ns();
+ u32 val = i2c_readl(i2c_dev, I2C_FIFO_CONTROL);
+ val |= I2C_FIFO_CONTROL_TX_FLUSH | I2C_FIFO_CONTROL_RX_FLUSH;
+ i2c_writel(i2c_dev, val, I2C_FIFO_CONTROL);
+
+ while (i2c_readl(i2c_dev, I2C_FIFO_CONTROL) &
+ (I2C_FIFO_CONTROL_TX_FLUSH | I2C_FIFO_CONTROL_RX_FLUSH)) {
+ if (is_timeout(start, SECOND)) {
+ dev_warn(i2c_dev->dev, "timeout waiting for fifo flush\n");
+ return -ETIMEDOUT;
+ }
+ mdelay(1);
+ }
+ return 0;
+}
+
+static int tegra_i2c_empty_rx_fifo(struct tegra_i2c_dev *i2c_dev)
+{
+ u32 val;
+ int rx_fifo_avail;
+ u8 *buf = i2c_dev->msg_buf;
+ size_t buf_remaining = i2c_dev->msg_buf_remaining;
+ int words_to_transfer;
+
+ val = i2c_readl(i2c_dev, I2C_FIFO_STATUS);
+ rx_fifo_avail = (val & I2C_FIFO_STATUS_RX_MASK) >>
+ I2C_FIFO_STATUS_RX_SHIFT;
+
+ /* Rounds down to not include partial word at the end of buf */
+ words_to_transfer = buf_remaining / BYTES_PER_FIFO_WORD;
+ if (words_to_transfer > rx_fifo_avail)
+ words_to_transfer = rx_fifo_avail;
+
+ i2c_readsl(i2c_dev, buf, I2C_RX_FIFO, words_to_transfer);
+
+ buf += words_to_transfer * BYTES_PER_FIFO_WORD;
+ buf_remaining -= words_to_transfer * BYTES_PER_FIFO_WORD;
+ rx_fifo_avail -= words_to_transfer;
+
+ /*
+ * If there is a partial word at the end of buf, handle it manually to
+ * prevent overwriting past the end of buf
+ */
+ if (rx_fifo_avail > 0 && buf_remaining > 0) {
+ BUG_ON(buf_remaining > 3);
+ val = i2c_readl(i2c_dev, I2C_RX_FIFO);
+ memcpy(buf, &val, buf_remaining);
+ buf_remaining = 0;
+ rx_fifo_avail--;
+ }
+
+ BUG_ON(rx_fifo_avail > 0 && buf_remaining > 0);
+ i2c_dev->msg_buf_remaining = buf_remaining;
+ i2c_dev->msg_buf = buf;
+ return 0;
+}
+
+static int tegra_i2c_fill_tx_fifo(struct tegra_i2c_dev *i2c_dev)
+{
+ u32 val;
+ int tx_fifo_avail;
+ u8 *buf = i2c_dev->msg_buf;
+ size_t buf_remaining = i2c_dev->msg_buf_remaining;
+ int words_to_transfer;
+
+ val = i2c_readl(i2c_dev, I2C_FIFO_STATUS);
+ tx_fifo_avail = (val & I2C_FIFO_STATUS_TX_MASK) >>
+ I2C_FIFO_STATUS_TX_SHIFT;
+
+ /* Rounds down to not include partial word at the end of buf */
+ words_to_transfer = buf_remaining / BYTES_PER_FIFO_WORD;
+
+ /* It's very common to have < 4 bytes, so optimize that case. */
+ if (words_to_transfer) {
+ if (words_to_transfer > tx_fifo_avail)
+ words_to_transfer = tx_fifo_avail;
+
+ /*
+ * Update state before writing to FIFO. If this casues us
+ * to finish writing all bytes (AKA buf_remaining goes to 0) we
+ * have a potential for an interrupt (PACKET_XFER_COMPLETE is
+ * not maskable). We need to make sure that the isr sees
+ * buf_remaining as 0 and doesn't call us back re-entrantly.
+ */
+ buf_remaining -= words_to_transfer * BYTES_PER_FIFO_WORD;
+ tx_fifo_avail -= words_to_transfer;
+ i2c_dev->msg_buf_remaining = buf_remaining;
+ i2c_dev->msg_buf = buf +
+ words_to_transfer * BYTES_PER_FIFO_WORD;
+ barrier();
+
+ i2c_writesl(i2c_dev, buf, I2C_TX_FIFO, words_to_transfer);
+
+ buf += words_to_transfer * BYTES_PER_FIFO_WORD;
+ }
+
+ /*
+ * If there is a partial word at the end of buf, handle it manually to
+ * prevent reading past the end of buf, which could cross a page
+ * boundary and fault.
+ */
+ if (tx_fifo_avail > 0 && buf_remaining > 0) {
+ BUG_ON(buf_remaining > 3);
+ memcpy(&val, buf, buf_remaining);
+
+ /* Again update before writing to FIFO to make sure isr sees. */
+ i2c_dev->msg_buf_remaining = 0;
+ i2c_dev->msg_buf = NULL;
+ barrier();
+
+ i2c_writel(i2c_dev, val, I2C_TX_FIFO);
+ }
+
+ return 0;
+}
+
+/*
+ * One of the Tegra I2C blocks is inside the DVC (Digital Voltage Controller)
+ * block. This block is identical to the rest of the I2C blocks, except that
+ * it only supports master mode, it has registers moved around, and it needs
+ * some extra init to get it into I2C mode. The register moves are handled
+ * by i2c_readl and i2c_writel
+ */
+static void tegra_dvc_init(struct tegra_i2c_dev *i2c_dev)
+{
+ u32 val = 0;
+ val = dvc_readl(i2c_dev, DVC_CTRL_REG3);
+ val |= DVC_CTRL_REG3_SW_PROG;
+ val |= DVC_CTRL_REG3_I2C_DONE_INTR_EN;
+ dvc_writel(i2c_dev, val, DVC_CTRL_REG3);
+
+ val = dvc_readl(i2c_dev, DVC_CTRL_REG1);
+ val |= DVC_CTRL_REG1_INTR_EN;
+ dvc_writel(i2c_dev, val, DVC_CTRL_REG1);
+}
+
+static inline int tegra_i2c_clock_enable(struct tegra_i2c_dev *i2c_dev)
+{
+ int ret;
+ if (!i2c_dev->hw->has_single_clk_source) {
+ ret = clk_enable(i2c_dev->fast_clk);
+ if (ret < 0) {
+ dev_err(i2c_dev->dev,
+ "Enabling fast clk failed, err %d\n", ret);
+ return ret;
+ }
+ }
+ ret = clk_enable(i2c_dev->div_clk);
+ if (ret < 0) {
+ dev_err(i2c_dev->dev,
+ "Enabling div clk failed, err %d\n", ret);
+ clk_disable(i2c_dev->fast_clk);
+ }
+ return ret;
+}
+
+static inline void tegra_i2c_clock_disable(struct tegra_i2c_dev *i2c_dev)
+{
+ clk_disable(i2c_dev->div_clk);
+ if (!i2c_dev->hw->has_single_clk_source)
+ clk_disable(i2c_dev->fast_clk);
+}
+
+static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)
+{
+ u32 val;
+ int err = 0;
+ int clk_multiplier = I2C_CLK_MULTIPLIER_STD_FAST_MODE;
+ u32 clk_divisor;
+
+ err = tegra_i2c_clock_enable(i2c_dev);
+ if (err < 0) {
+ dev_err(i2c_dev->dev, "Clock enable failed %d\n", err);
+ return err;
+ }
+
+ reset_control_assert(i2c_dev->rst);
+ udelay(2);
+ reset_control_deassert(i2c_dev->rst);
+
+ if (i2c_dev->is_dvc)
+ tegra_dvc_init(i2c_dev);
+
+ val = I2C_CNFG_NEW_MASTER_FSM | I2C_CNFG_PACKET_MODE_EN |
+ (0x2 << I2C_CNFG_DEBOUNCE_CNT_SHIFT);
+ i2c_writel(i2c_dev, val, I2C_CNFG);
+ i2c_writel(i2c_dev, 0, I2C_INT_MASK);
+
+ clk_multiplier *= (i2c_dev->hw->clk_divisor_std_fast_mode + 1);
+ clk_set_rate(i2c_dev->div_clk, i2c_dev->bus_clk_rate * clk_multiplier);
+
+ /* Make sure clock divisor programmed correctly */
+ clk_divisor = i2c_dev->hw->clk_divisor_hs_mode;
+ clk_divisor |= i2c_dev->hw->clk_divisor_std_fast_mode <<
+ I2C_CLK_DIVISOR_STD_FAST_MODE_SHIFT;
+ i2c_writel(i2c_dev, clk_divisor, I2C_CLK_DIVISOR);
+
+ if (!i2c_dev->is_dvc) {
+ u32 sl_cfg = i2c_readl(i2c_dev, I2C_SL_CNFG);
+ sl_cfg |= I2C_SL_CNFG_NACK | I2C_SL_CNFG_NEWSL;
+ i2c_writel(i2c_dev, sl_cfg, I2C_SL_CNFG);
+ i2c_writel(i2c_dev, 0xfc, I2C_SL_ADDR1);
+ i2c_writel(i2c_dev, 0x00, I2C_SL_ADDR2);
+ }
+
+ val = 7 << I2C_FIFO_CONTROL_TX_TRIG_SHIFT |
+ 0 << I2C_FIFO_CONTROL_RX_TRIG_SHIFT;
+ i2c_writel(i2c_dev, val, I2C_FIFO_CONTROL);
+
+ if (tegra_i2c_flush_fifos(i2c_dev))
+ err = -ETIMEDOUT;
+
+ tegra_i2c_clock_disable(i2c_dev);
+
+ return err;
+}
+
+static int tegra_i2c_wait_completion(struct tegra_i2c_dev *i2c_dev)
+{
+ u32 status;
+ const u32 status_err = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST;
+ uint64_t start = get_time_ns();
+
+ while (!is_timeout(start, SECOND)) {
+
+ status = i2c_readl(i2c_dev, I2C_INT_STATUS);
+
+ if (unlikely(status & status_err)) {
+ if (status & I2C_INT_NO_ACK)
+ i2c_dev->msg_err |= I2C_ERR_NO_ACK;
+ if (status & I2C_INT_ARBITRATION_LOST)
+ i2c_dev->msg_err |= I2C_ERR_ARBITRATION_LOST;
+ goto err;
+ }
+
+ if (i2c_dev->msg_read && (status & I2C_INT_RX_FIFO_DATA_REQ)) {
+ if (i2c_dev->msg_buf_remaining)
+ tegra_i2c_empty_rx_fifo(i2c_dev);
+ else
+ BUG();
+ }
+
+ if (!i2c_dev->msg_read && (status & I2C_INT_TX_FIFO_DATA_REQ)) {
+ if (i2c_dev->msg_buf_remaining)
+ tegra_i2c_fill_tx_fifo(i2c_dev);
+ }
+
+ i2c_writel(i2c_dev, status, I2C_INT_STATUS);
+ if (i2c_dev->is_dvc)
+ dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR,
+ DVC_STATUS);
+
+ if (status & I2C_INT_PACKET_XFER_COMPLETE) {
+ BUG_ON(i2c_dev->msg_buf_remaining);
+ return 0;
+ }
+ }
+ return -ETIMEDOUT;
+
+err:
+ i2c_writel(i2c_dev, status, I2C_INT_STATUS);
+ if (i2c_dev->is_dvc)
+ dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);
+
+ return 0;
+}
+
+static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
+ struct i2c_msg *msg, enum msg_end_type end_state)
+{
+ u32 packet_header;
+ int ret;
+
+ tegra_i2c_flush_fifos(i2c_dev);
+
+ if (msg->len == 0)
+ return -EINVAL;
+
+ i2c_dev->msg_buf = msg->buf;
+ i2c_dev->msg_buf_remaining = msg->len;
+ i2c_dev->msg_err = I2C_ERR_NONE;
+ i2c_dev->msg_read = (msg->flags & I2C_M_RD);
+
+ packet_header = (0 << PACKET_HEADER0_HEADER_SIZE_SHIFT) |
+ PACKET_HEADER0_PROTOCOL_I2C |
+ (i2c_dev->adapter.nr << PACKET_HEADER0_CONT_ID_SHIFT) |
+ (1 << PACKET_HEADER0_PACKET_ID_SHIFT);
+ i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);
+
+ packet_header = msg->len - 1;
+ i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);
+
+ packet_header = I2C_HEADER_IE_ENABLE;
+ if (end_state == MSG_END_CONTINUE)
+ packet_header |= I2C_HEADER_CONTINUE_XFER;
+ else if (end_state == MSG_END_REPEAT_START)
+ packet_header |= I2C_HEADER_REPEAT_START;
+ if (msg->flags & I2C_M_TEN) {
+ packet_header |= msg->addr;
+ packet_header |= I2C_HEADER_10BIT_ADDR;
+ } else {
+ packet_header |= msg->addr << I2C_HEADER_SLAVE_ADDR_SHIFT;
+ }
+ if (msg->flags & I2C_M_IGNORE_NAK)
+ packet_header |= I2C_HEADER_CONT_ON_NAK;
+ if (msg->flags & I2C_M_RD)
+ packet_header |= I2C_HEADER_READ;
+ i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);
+
+ if (!(msg->flags & I2C_M_RD))
+ tegra_i2c_fill_tx_fifo(i2c_dev);
+
+ ret = tegra_i2c_wait_completion(i2c_dev);
+ if (ret) {
+ dev_err(i2c_dev->dev, "i2c transfer timed out\n");
+
+ tegra_i2c_init(i2c_dev);
+ return -ETIMEDOUT;
+ }
+
+ if (likely(i2c_dev->msg_err == I2C_ERR_NONE))
+ return 0;
+
+ /*
+ * NACK interrupt is generated before the I2C controller generates the
+ * STOP condition on the bus. So wait for 2 clock periods before resetting
+ * the controller so that STOP condition has been delivered properly.
+ */
+ if (i2c_dev->msg_err == I2C_ERR_NO_ACK)
+ udelay(DIV_ROUND_UP(2 * 1000000, i2c_dev->bus_clk_rate));
+
+ tegra_i2c_init(i2c_dev);
+ if (i2c_dev->msg_err == I2C_ERR_NO_ACK) {
+ if (msg->flags & I2C_M_IGNORE_NAK)
+ return 0;
+ return -EREMOTEIO;
+ }
+
+ return -EIO;
+}
+
+static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
+ int num)
+{
+ struct tegra_i2c_dev *i2c_dev = to_tegra_i2c_dev(adap);
+ int i;
+ int ret = 0;
+
+ ret = tegra_i2c_clock_enable(i2c_dev);
+ if (ret < 0) {
+ dev_err(i2c_dev->dev, "Clock enable failed %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < num; i++) {
+ enum msg_end_type end_type = MSG_END_STOP;
+ if (i < (num - 1))
+ end_type = MSG_END_REPEAT_START;
+ ret = tegra_i2c_xfer_msg(i2c_dev, &msgs[i], end_type);
+ if (ret)
+ break;
+ }
+ tegra_i2c_clock_disable(i2c_dev);
+ return ret ? ret : i;
+}
+
+static const struct tegra_i2c_hw_feature tegra20_i2c_hw = {
+ .has_continue_xfer_support = false,
+ .has_per_pkt_xfer_complete_irq = false,
+ .has_single_clk_source = false,
+ .clk_divisor_hs_mode = 3,
+ .clk_divisor_std_fast_mode = 0,
+};
+
+static const struct tegra_i2c_hw_feature tegra30_i2c_hw = {
+ .has_continue_xfer_support = true,
+ .has_per_pkt_xfer_complete_irq = false,
+ .has_single_clk_source = false,
+ .clk_divisor_hs_mode = 3,
+ .clk_divisor_std_fast_mode = 0,
+};
+
+static const struct tegra_i2c_hw_feature tegra114_i2c_hw = {
+ .has_continue_xfer_support = true,
+ .has_per_pkt_xfer_complete_irq = true,
+ .has_single_clk_source = true,
+ .clk_divisor_hs_mode = 1,
+ .clk_divisor_std_fast_mode = 0x19,
+};
+
+static int tegra_i2c_probe(struct device_d *dev)
+{
+ struct tegra_i2c_dev *i2c_dev;
+ struct clk *div_clk, *fast_clk;
+ void __iomem *base;
+ int ret = 0;
+
+ base = dev_request_mem_region(dev, 0);
+ if (!base) {
+ dev_err(dev, "could not get iomem region\n");
+ return -ENODEV;
+ }
+
+ div_clk = clk_get(dev, "div-clk");
+ if (IS_ERR(div_clk)) {
+ dev_err(dev, "missing controller clock");
+ return PTR_ERR(div_clk);
+ }
+
+ i2c_dev = xzalloc(sizeof(*i2c_dev));
+
+ i2c_dev->base = base;
+ i2c_dev->div_clk = div_clk;
+ i2c_dev->dev = dev;
+
+ i2c_dev->rst = reset_control_get(dev, "i2c");
+ if (IS_ERR(i2c_dev->rst)) {
+ dev_err(dev, "missing controller reset");
+ return PTR_ERR(i2c_dev->rst);
+ }
+
+ ret = of_property_read_u32(dev->device_node, "clock-frequency",
+ &i2c_dev->bus_clk_rate);
+ if (ret)
+ i2c_dev->bus_clk_rate = 100000; /* default clock rate */
+
+ i2c_dev->hw = &tegra20_i2c_hw;
+ dev_get_drvdata(dev, (unsigned long *)&i2c_dev->hw);
+ i2c_dev->is_dvc = of_device_is_compatible(dev->device_node,
+ "nvidia,tegra20-i2c-dvc");
+
+ if (!i2c_dev->hw->has_single_clk_source) {
+ fast_clk = clk_get(dev, "fast-clk");
+ if (IS_ERR(fast_clk)) {
+ dev_err(dev, "missing fast clock");
+ return PTR_ERR(fast_clk);
+ }
+ i2c_dev->fast_clk = fast_clk;
+ }
+
+ ret = tegra_i2c_init(i2c_dev);
+ if (ret) {
+ dev_err(dev, "Failed to initialize i2c controller");
+ return ret;
+ }
+
+ i2c_dev->adapter.master_xfer = tegra_i2c_xfer;
+ i2c_dev->adapter.dev.parent = dev;
+ i2c_dev->adapter.nr = dev->id;
+ i2c_dev->adapter.dev.device_node = dev->device_node;
+
+ ret = i2c_add_numbered_adapter(&i2c_dev->adapter);
+ if (ret) {
+ dev_err(dev, "Failed to add I2C adapter\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static __maybe_unused struct of_device_id tegra_i2c_compatible[] = {
+ {
+ .compatible = "nvidia,tegra114-i2c",
+ .data = (unsigned long)&tegra114_i2c_hw,
+ }, {
+ .compatible = "nvidia,tegra30-i2c",
+ .data = (unsigned long)&tegra30_i2c_hw,
+ }, {
+ .compatible = "nvidia,tegra20-i2c",
+ .data = (unsigned long)&tegra20_i2c_hw,
+ }, {
+ .compatible = "nvidia,tegra20-i2c-dvc",
+ .data = (unsigned long)&tegra20_i2c_hw,
+ }, {
+ /* sentinel */
+ }
+};
+
+static struct driver_d tegra_i2c_driver = {
+ .name = "tegra-i2c",
+ .probe = tegra_i2c_probe,
+ .of_compatible = DRV_OF_COMPAT(tegra_i2c_compatible),
+};
+device_platform_driver(tegra_i2c_driver);
diff --git a/drivers/mci/mci-core.c b/drivers/mci/mci-core.c
index 282d2399f7..ce6e590aaf 100644
--- a/drivers/mci/mci-core.c
+++ b/drivers/mci/mci-core.c
@@ -1572,7 +1572,8 @@ static int mci_card_probe(struct mci *mci)
struct mci_host *host = mci->host;
int i, rc, disknum, ret;
- if (host->card_present && !host->card_present(host)) {
+ if (host->card_present && !host->card_present(host) &&
+ !host->non_removable) {
dev_err(&mci->dev, "no card inserted\n");
return -ENODEV;
}
@@ -1839,4 +1840,6 @@ void mci_of_parse(struct mci_host *host)
host->dsr_val = dsr_val;
}
}
+
+ host->non_removable = of_property_read_bool(np, "non-removable");
}
diff --git a/drivers/mci/tegra-sdmmc.c b/drivers/mci/tegra-sdmmc.c
index e4d82197bd..62cf7305d3 100644
--- a/drivers/mci/tegra-sdmmc.c
+++ b/drivers/mci/tegra-sdmmc.c
@@ -28,6 +28,7 @@
#include <mci.h>
#include <of_gpio.h>
#include <linux/clk.h>
+#include <linux/reset.h>
#include "sdhci.h"
@@ -58,10 +59,19 @@
#define TEGRA_SDMMC_INT_SIG_EN 0x038
#define TEGRA_SDMMC_INT_SIG_EN_XFER_COMPLETE (1 << 1)
+#define TEGRA_SDMMC_SDMEMCOMPPADCTRL 0x1e0
+#define TEGRA_SDMMC_SDMEMCOMPPADCTRL_VREF_SEL_SHIFT 0
+
+#define TEGRA_SDMMC_AUTO_CAL_CONFIG 0x1e4
+#define TEGRA_SDMMC_AUTO_CAL_CONFIG_PU_OFFSET_SHIFT 0
+#define TEGRA_SDMMC_AUTO_CAL_CONFIG_PD_OFFSET_SHIFT 8
+#define TEGRA_SDMMC_AUTO_CAL_CONFIG_ENABLE (1 << 29)
+
struct tegra_sdmmc_host {
struct mci_host mci;
void __iomem *regs;
struct clk *clk;
+ struct reset_control *reset;
int gpio_cd, gpio_pwr;
};
#define to_tegra_sdmmc_host(mci) container_of(mci, struct tegra_sdmmc_host, mci)
@@ -331,6 +341,23 @@ static int tegra_sdmmc_init(struct mci_host *mci, struct device_d *dev)
val |= TEGRA_SDMMC_PWR_CNTL_33_V | TEGRA_SDMMC_PWR_CNTL_SD_BUS;
writel(val, regs + TEGRA_SDMMC_PWR_CNTL);
+ /* sdmmc1 and sdmmc3 on T30 need a bit of padctrl init */
+ if (of_device_is_compatible(mci->hw_dev->device_node,
+ "nvidia,tegra30-sdhci") &&
+ ((u32)regs == 0x78000000 || (u32)regs == 78000400)) {
+ val = readl(regs + TEGRA_SDMMC_SDMEMCOMPPADCTRL);
+ val &= 0xfffffff0;
+ val |= 0x7 << TEGRA_SDMMC_SDMEMCOMPPADCTRL_VREF_SEL_SHIFT;
+ writel(val, regs + TEGRA_SDMMC_SDMEMCOMPPADCTRL);
+
+ val = readl(regs + TEGRA_SDMMC_AUTO_CAL_CONFIG);
+ val &= 0xffff0000;
+ val |= (0x62 << TEGRA_SDMMC_AUTO_CAL_CONFIG_PU_OFFSET_SHIFT) |
+ (0x70 << TEGRA_SDMMC_AUTO_CAL_CONFIG_PD_OFFSET_SHIFT) |
+ TEGRA_SDMMC_AUTO_CAL_CONFIG_ENABLE;
+ writel(val, regs + TEGRA_SDMMC_AUTO_CAL_CONFIG);
+ }
+
/* setup signaling */
writel(0xffffffff, regs + TEGRA_SDMMC_INT_STAT_EN);
writel(0xffffffff, regs + TEGRA_SDMMC_INT_SIG_EN);
@@ -400,6 +427,10 @@ static int tegra_sdmmc_probe(struct device_d *dev)
if (IS_ERR(host->clk))
return PTR_ERR(host->clk);
+ host->reset = reset_control_get(dev, NULL);
+ if (IS_ERR(host->reset))
+ return PTR_ERR(host->reset);
+
host->regs = dev_request_mem_region(dev, 0);
if (!host->regs) {
dev_err(dev, "could not get iomem region\n");
@@ -430,14 +461,16 @@ static int tegra_sdmmc_probe(struct device_d *dev)
}
clk_enable(host->clk);
+ reset_control_assert(host->reset);
+ udelay(2);
+ reset_control_deassert(host->reset);
mci->init = tegra_sdmmc_init;
mci->card_present = tegra_sdmmc_card_present;
mci->set_ios = tegra_sdmmc_set_ios;
mci->send_cmd = tegra_sdmmc_send_cmd;
mci->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
- mci->host_caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA |
- MMC_CAP_MMC_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED_52MHZ |
+ mci->host_caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED_52MHZ |
MMC_CAP_SD_HIGHSPEED;
dev->priv = host;
diff --git a/drivers/pinctrl/pinctrl-tegra30.c b/drivers/pinctrl/pinctrl-tegra30.c
index 032377fe8c..888bf04b04 100644
--- a/drivers/pinctrl/pinctrl-tegra30.c
+++ b/drivers/pinctrl/pinctrl-tegra30.c
@@ -37,6 +37,23 @@ struct tegra30_pingroup {
u16 reg;
};
+struct tegra30_drive_pingroup {
+ const char *name;
+ u16 reg;
+ u32 hsm_bit:5;
+ u32 schmitt_bit:5;
+ u32 lpmd_bit:5;
+ u32 drvdn_bit:5;
+ u32 drvup_bit:5;
+ u32 slwr_bit:5;
+ u32 slwf_bit:5;
+ u32 drvtype_bit:5;
+ u32 drvdn_width:6;
+ u32 drvup_width:6;
+ u32 slwr_width:6;
+ u32 slwf_width:6;
+};
+
#define PG(pg_name, f0, f1, f2, f3, offset) \
{ \
.name = #pg_name, \
@@ -44,6 +61,25 @@ struct tegra30_pingroup {
.reg = offset \
}
+#define DRV_PG(pg_name, r, hsm_b, schmitt_b, lpmd_b, \
+ drvdn_b, drvdn_w, drvup_b, drvup_w, \
+ slwr_b, slwr_w, slwf_b, slwf_w) \
+ { \
+ .name = "drive_" #pg_name, \
+ .reg = r - 0x868, \
+ .hsm_bit = hsm_b, \
+ .schmitt_bit = schmitt_b, \
+ .lpmd_bit = lpmd_b, \
+ .drvdn_bit = drvdn_b, \
+ .drvdn_width = drvdn_w, \
+ .drvup_bit = drvup_b, \
+ .drvup_width = drvup_w, \
+ .slwr_bit = slwr_b, \
+ .slwr_width = slwr_w, \
+ .slwf_bit = slwf_b, \
+ .slwf_width = slwf_w, \
+ }
+
static const struct tegra30_pingroup tegra30_groups[] = {
/* name, f0, f1, f2, f3, reg */
PG(clk_32k_out_pa0, blink, rsvd2, rsvd3, rsvd4, 0x31c),
@@ -297,6 +333,122 @@ static const struct tegra30_pingroup tegra30_groups[] = {
PG(pwr_int_n, pwr_int_n, rsvd2, rsvd3, rsvd4, 0x32c),
};
+static const struct tegra30_drive_pingroup tegra30_drive_groups[] = {
+ DRV_PG(ao1, 0x868, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(ao2, 0x86c, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(at1, 0x870, 2, 3, 4, 14, 5, 19, 5, 24, 2, 28, 2),
+ DRV_PG(at2, 0x874, 2, 3, 4, 14, 5, 19, 5, 24, 2, 28, 2),
+ DRV_PG(at3, 0x878, 2, 3, 4, 14, 5, 19, 5, 28, 2, 30, 2),
+ DRV_PG(at4, 0x87c, 2, 3, 4, 14, 5, 19, 5, 28, 2, 30, 2),
+ DRV_PG(at5, 0x880, 2, 3, 4, 14, 5, 19, 5, 28, 2, 30, 2),
+ DRV_PG(cdev1, 0x884, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(cdev2, 0x888, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(cec, 0x938, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(crt, 0x8f8, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(csus, 0x88c, -1, -1, -1, 12, 5, 19, 5, 24, 4, 28, 4),
+ DRV_PG(dap1, 0x890, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(dap2, 0x894, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(dap3, 0x898, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(dap4, 0x89c, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(dbg, 0x8a0, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(ddc, 0x8fc, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(dev3, 0x92c, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(gma, 0x900, -1, -1, -1, 14, 5, 19, 5, 24, 4, 28, 4),
+ DRV_PG(gmb, 0x904, -1, -1, -1, 14, 5, 19, 5, 24, 4, 28, 4),
+ DRV_PG(gmc, 0x908, -1, -1, -1, 14, 5, 19, 5, 24, 4, 28, 4),
+ DRV_PG(gmd, 0x90c, -1, -1, -1, 14, 5, 19, 5, 24, 4, 28, 4),
+ DRV_PG(gme, 0x910, 2, 3, 4, 14, 5, 19, 5, 28, 2, 30, 2),
+ DRV_PG(gmf, 0x914, 2, 3, 4, 14, 5, 19, 5, 28, 2, 30, 2),
+ DRV_PG(gmg, 0x918, 2, 3, 4, 14, 5, 19, 5, 28, 2, 30, 2),
+ DRV_PG(gmh, 0x91c, 2, 3, 4, 14, 5, 19, 5, 28, 2, 30, 2),
+ DRV_PG(gpv, 0x928, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(lcd1, 0x8a4, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(lcd2, 0x8a8, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(owr, 0x920, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(sdio1, 0x8ec, 2, 3, -1, 12, 7, 20, 7, 28, 2, 30, 2),
+ DRV_PG(sdio2, 0x8ac, 2, 3, -1, 12, 7, 20, 7, 28, 2, 30, 2),
+ DRV_PG(sdio3, 0x8b0, 2, 3, -1, 12, 7, 20, 7, 28, 2, 30, 2),
+ DRV_PG(spi, 0x8b4, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(uaa, 0x8b8, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(uab, 0x8bc, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(uart2, 0x8c0, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(uart3, 0x8c4, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(uda, 0x924, 2, 3, 4, 12, 5, 20, 5, 28, 2, 30, 2),
+ DRV_PG(vi1, 0x8c8, -1, -1, -1, 14, 5, 19, 5, 24, 4, 28, 4),
+};
+
+static int pinctrl_tegra30_set_drvstate(struct pinctrl_tegra30 *ctrl,
+ struct device_node *np)
+{
+ const char *pins = NULL;
+ const struct tegra30_drive_pingroup *group = NULL;
+ int hsm = -1, schmitt = -1, pds = -1, pus = -1, srr = -1, srf = -1;
+ int i;
+ u32 __iomem *regaddr;
+ u32 val;
+
+ if (of_property_read_string(np, "nvidia,pins", &pins))
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(tegra30_drive_groups); i++) {
+ if (!strcmp(pins, tegra30_drive_groups[i].name)) {
+ group = &tegra30_drive_groups[i];
+ break;
+ }
+ }
+ /* if no matching drivegroup is found */
+ if (i == ARRAY_SIZE(tegra30_groups))
+ return 0;
+
+ regaddr = ctrl->regs.ctrl + (group->reg >> 2);
+
+ of_property_read_u32_array(np, "nvidia,high-speed-mode", &hsm, 1);
+ of_property_read_u32_array(np, "nvidia,schmitt", &schmitt, 1);
+ of_property_read_u32_array(np, "nvidia,pull-down-strength", &pds, 1);
+ of_property_read_u32_array(np, "nvidia,pull-up-strength", &pus, 1);
+ of_property_read_u32_array(np, "nvidia,slew-rate-rising", &srr, 1);
+ of_property_read_u32_array(np, "nvidia,slew-rate-falling", &srf, 1);
+
+ if (hsm >= 0) {
+ val = readl(regaddr);
+ val &= ~(0x1 << group->hsm_bit);
+ val |= hsm << group->hsm_bit;
+ writel(val, regaddr);
+ }
+ if (schmitt >= 0) {
+ val = readl(regaddr);
+ val &= ~(0x1 << group->schmitt_bit);
+ val |= hsm << group->schmitt_bit;
+ writel(val, regaddr);
+ }
+ if (pds >= 0) {
+ val = readl(regaddr);
+ val &= ~(((1 << group->drvdn_width) - 1) << group->drvdn_bit);
+ val |= hsm << group->drvdn_bit;
+ writel(val, regaddr);
+ }
+ if (pus >= 0) {
+ val = readl(regaddr);
+ val &= ~(((1 << group->drvup_width) - 1) << group->drvup_bit);
+ val |= hsm << group->drvup_bit;
+ writel(val, regaddr);
+ }
+ if (srr >= 0) {
+ val = readl(regaddr);
+ val &= ~(((1 << group->slwr_width) - 1) << group->slwr_bit);
+ val |= hsm << group->slwr_bit;
+ writel(val, regaddr);
+ }
+ if (srf >= 0) {
+ val = readl(regaddr);
+ val &= ~(((1 << group->slwf_width) - 1) << group->slwf_bit);
+ val |= hsm << group->slwf_bit;
+ writel(val, regaddr);
+ }
+
+ return 1;
+}
+
static void pinctrl_tegra30_set_func(struct pinctrl_tegra30 *ctrl,
u32 reg, int func)
{
@@ -405,8 +557,13 @@ static int pinctrl_tegra30_set_state(struct pinctrl_device *pdev,
break;
}
}
- /* if no matching pingroup is found bail out */
+ /* if no matching pingroup is found */
if (j == ARRAY_SIZE(tegra30_groups)) {
+ /* see if we can find a drivegroup */
+ if (pinctrl_tegra30_set_drvstate(ctrl, np))
+ continue;
+
+ /* nothing matching found, warn and bail out */
dev_warn(ctrl->pinctrl.dev,
"invalid pingroup %s referenced in node %s\n",
pins, np->name);
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
new file mode 100644
index 0000000000..c9d04f7978
--- /dev/null
+++ b/drivers/reset/Kconfig
@@ -0,0 +1,13 @@
+config ARCH_HAS_RESET_CONTROLLER
+ bool
+
+menuconfig RESET_CONTROLLER
+ bool "Reset Controller Support"
+ default y if ARCH_HAS_RESET_CONTROLLER
+ help
+ Generic Reset Controller support.
+
+ This framework is designed to abstract reset handling of devices
+ via GPIOs or SoC-internal reset controller modules.
+
+ If unsure, say no.
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
new file mode 100644
index 0000000000..1e2d83f2b9
--- /dev/null
+++ b/drivers/reset/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_RESET_CONTROLLER) += core.o
diff --git a/drivers/reset/core.c b/drivers/reset/core.c
new file mode 100644
index 0000000000..101913aefe
--- /dev/null
+++ b/drivers/reset/core.c
@@ -0,0 +1,236 @@
+/*
+ * Reset Controller framework
+ *
+ * Copyright 2013 Philipp Zabel, Pengutronix
+ *
+ * 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.
+ */
+#include <common.h>
+#include <malloc.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/reset.h>
+#include <linux/reset-controller.h>
+
+static LIST_HEAD(reset_controller_list);
+
+/**
+ * struct reset_control - a reset control
+ * @rcdev: a pointer to the reset controller device
+ * this reset control belongs to
+ * @id: ID of the reset controller in the reset
+ * controller device
+ */
+struct reset_control {
+ struct reset_controller_dev *rcdev;
+ struct device_d *dev;
+ unsigned int id;
+};
+
+/**
+ * of_reset_simple_xlate - translate reset_spec to the reset line number
+ * @rcdev: a pointer to the reset controller device
+ * @reset_spec: reset line specifier as found in the device tree
+ * @flags: a flags pointer to fill in (optional)
+ *
+ * This simple translation function should be used for reset controllers
+ * with 1:1 mapping, where reset lines can be indexed by number without gaps.
+ */
+static int of_reset_simple_xlate(struct reset_controller_dev *rcdev,
+ const struct of_phandle_args *reset_spec)
+{
+ if (WARN_ON(reset_spec->args_count != rcdev->of_reset_n_cells))
+ return -EINVAL;
+
+ if (reset_spec->args[0] >= rcdev->nr_resets)
+ return -EINVAL;
+
+ return reset_spec->args[0];
+}
+
+/**
+ * reset_controller_register - register a reset controller device
+ * @rcdev: a pointer to the initialized reset controller device
+ */
+int reset_controller_register(struct reset_controller_dev *rcdev)
+{
+ if (!rcdev->of_xlate) {
+ rcdev->of_reset_n_cells = 1;
+ rcdev->of_xlate = of_reset_simple_xlate;
+ }
+
+ list_add(&rcdev->list, &reset_controller_list);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(reset_controller_register);
+
+/**
+ * reset_controller_unregister - unregister a reset controller device
+ * @rcdev: a pointer to the reset controller device
+ */
+void reset_controller_unregister(struct reset_controller_dev *rcdev)
+{
+ list_del(&rcdev->list);
+}
+EXPORT_SYMBOL_GPL(reset_controller_unregister);
+
+/**
+ * reset_control_reset - reset the controlled device
+ * @rstc: reset controller
+ */
+int reset_control_reset(struct reset_control *rstc)
+{
+ if (rstc->rcdev->ops->reset)
+ return rstc->rcdev->ops->reset(rstc->rcdev, rstc->id);
+
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(reset_control_reset);
+
+/**
+ * reset_control_assert - asserts the reset line
+ * @rstc: reset controller
+ */
+int reset_control_assert(struct reset_control *rstc)
+{
+ if (rstc->rcdev->ops->assert)
+ return rstc->rcdev->ops->assert(rstc->rcdev, rstc->id);
+
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(reset_control_assert);
+
+/**
+ * reset_control_deassert - deasserts the reset line
+ * @rstc: reset controller
+ */
+int reset_control_deassert(struct reset_control *rstc)
+{
+ if (rstc->rcdev->ops->deassert)
+ return rstc->rcdev->ops->deassert(rstc->rcdev, rstc->id);
+
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(reset_control_deassert);
+
+/**
+ * of_reset_control_get - Lookup and obtain a reference to a reset controller.
+ * @node: device to be reset by the controller
+ * @id: reset line name
+ *
+ * Returns a struct reset_control or IS_ERR() condition containing errno.
+ *
+ * Use of id names is optional.
+ */
+struct reset_control *of_reset_control_get(struct device_node *node,
+ const char *id)
+{
+ struct reset_control *rstc = ERR_PTR(-ENODEV);
+ struct reset_controller_dev *r, *rcdev;
+ struct of_phandle_args args;
+ int index = 0;
+ int rstc_id;
+ int ret;
+
+ if (id)
+ index = of_property_match_string(node,
+ "reset-names", id);
+ ret = of_parse_phandle_with_args(node, "resets", "#reset-cells",
+ index, &args);
+ if (ret)
+ return ERR_PTR(ret);
+
+ rcdev = NULL;
+ list_for_each_entry(r, &reset_controller_list, list) {
+ if (args.np == r->of_node) {
+ rcdev = r;
+ break;
+ }
+ }
+
+ if (!rcdev) {
+ return ERR_PTR(-ENODEV);
+ }
+
+ rstc_id = rcdev->of_xlate(rcdev, &args);
+ if (rstc_id < 0)
+ return ERR_PTR(rstc_id);
+
+ rstc = kzalloc(sizeof(*rstc), GFP_KERNEL);
+ if (!rstc)
+ return ERR_PTR(-ENOMEM);
+
+ rstc->rcdev = rcdev;
+ rstc->id = rstc_id;
+
+ return rstc;
+}
+EXPORT_SYMBOL_GPL(of_reset_control_get);
+
+/**
+ * reset_control_get - Lookup and obtain a reference to a reset controller.
+ * @dev: device to be reset by the controller
+ * @id: reset line name
+ *
+ * Returns a struct reset_control or IS_ERR() condition containing errno.
+ *
+ * Use of id names is optional.
+ */
+struct reset_control *reset_control_get(struct device_d *dev, const char *id)
+{
+ struct reset_control *rstc;
+
+ if (!dev)
+ return ERR_PTR(-EINVAL);
+
+ rstc = of_reset_control_get(dev->device_node, id);
+ if (!IS_ERR(rstc))
+ rstc->dev = dev;
+
+ return rstc;
+}
+EXPORT_SYMBOL_GPL(reset_control_get);
+
+/**
+ * reset_control_put - free the reset controller
+ * @rstc: reset controller
+ */
+
+void reset_control_put(struct reset_control *rstc)
+{
+ if (IS_ERR(rstc))
+ return;
+
+ kfree(rstc);
+}
+EXPORT_SYMBOL_GPL(reset_control_put);
+
+/**
+ * device_reset - find reset controller associated with the device
+ * and perform reset
+ * @dev: device to be reset by the controller
+ *
+ * Convenience wrapper for reset_control_get() and reset_control_reset().
+ * This is useful for the common case of devices with single, dedicated reset
+ * lines.
+ */
+int device_reset(struct device_d *dev)
+{
+ struct reset_control *rstc;
+ int ret;
+
+ rstc = reset_control_get(dev, NULL);
+ if (IS_ERR(rstc))
+ return PTR_ERR(rstc);
+
+ ret = reset_control_reset(rstc);
+
+ reset_control_put(rstc);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(device_reset);