diff options
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/Kconfig | 10 | ||||
-rw-r--r-- | drivers/i2c/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/tegra186_bpmp_i2c.c | 129 | ||||
-rw-r--r-- | drivers/i2c/tegra_i2c.c | 89 |
4 files changed, 225 insertions, 4 deletions
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 8575a23fba..1537b673fe 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -171,6 +171,16 @@ config SYS_I2C_MVTWSI Support for Marvell I2C controllers as used on the orion5x and kirkwood SoC families. +config TEGRA186_BPMP_I2C + bool "Enable Tegra186 BPMP-based I2C driver" + depends on TEGRA186_BPMP + help + Support for Tegra I2C controllers managed by the BPMP (Boot and + Power Management Processor). On Tegra186, some I2C controllers are + directly controlled by the main CPU, whereas others are controlled + by the BPMP, and can only be accessed by the main CPU via IPC + requests to the BPMP. This driver covers the latter case. + source "drivers/i2c/muxes/Kconfig" endmenu diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index c5362a5a46..2987081991 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -42,5 +42,6 @@ obj-$(CONFIG_SYS_I2C_TEGRA) += tegra_i2c.o obj-$(CONFIG_SYS_I2C_UNIPHIER) += i2c-uniphier.o obj-$(CONFIG_SYS_I2C_UNIPHIER_F) += i2c-uniphier-f.o obj-$(CONFIG_SYS_I2C_ZYNQ) += zynq_i2c.o +obj-$(CONFIG_TEGRA186_BPMP_I2C) += tegra186_bpmp_i2c.o obj-$(CONFIG_I2C_MUX) += muxes/ diff --git a/drivers/i2c/tegra186_bpmp_i2c.c b/drivers/i2c/tegra186_bpmp_i2c.c new file mode 100644 index 0000000000..88e8413d9e --- /dev/null +++ b/drivers/i2c/tegra186_bpmp_i2c.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <common.h> +#include <dm.h> +#include <i2c.h> +#include <misc.h> +#include <asm/arch-tegra/bpmp_abi.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct tegra186_bpmp_i2c { + uint32_t bpmp_bus_id; +}; + +static inline void serialize_u16(uint8_t **p, uint16_t val) +{ + (*p)[0] = val & 0xff; + (*p)[1] = val >> 8; + (*p) += 2; +} + +/* These just happen to have the same values as I2C_M_* and SERIALI2C_* */ +#define SUPPORTED_FLAGS \ + (I2C_M_TEN | \ + I2C_M_RD | \ + I2C_M_STOP | \ + I2C_M_NOSTART | \ + I2C_M_REV_DIR_ADDR | \ + I2C_M_IGNORE_NAK | \ + I2C_M_NO_RD_ACK | \ + I2C_M_RECV_LEN) + +static int tegra186_bpmp_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, + int nmsgs) +{ + struct tegra186_bpmp_i2c *priv = dev_get_priv(dev); + struct mrq_i2c_request req; + struct mrq_i2c_response resp; + uint8_t *p; + int left, i, ret; + + req.cmd = CMD_I2C_XFER; + req.xfer.bus_id = priv->bpmp_bus_id; + p = &req.xfer.data_buf[0]; + left = ARRAY_SIZE(req.xfer.data_buf); + for (i = 0; i < nmsgs; i++) { + int len = 6; + if (!(msg[i].flags & I2C_M_RD)) + len += msg[i].len; + if ((len >= BIT(16)) || (len > left)) + return -ENOSPC; + + if (msg[i].flags & ~SUPPORTED_FLAGS) + return -EINVAL; + + serialize_u16(&p, msg[i].addr); + serialize_u16(&p, msg[i].flags); + serialize_u16(&p, msg[i].len); + if (!(msg[i].flags & I2C_M_RD)) { + memcpy(p, msg[i].buf, msg[i].len); + p += msg[i].len; + } + } + req.xfer.data_size = p - &req.xfer.data_buf[0]; + + ret = misc_call(dev->parent, MRQ_I2C, &req, sizeof(req), &resp, + sizeof(resp)); + if (ret < 0) + return ret; + + p = &resp.xfer.data_buf[0]; + left = resp.xfer.data_size; + if (left > ARRAY_SIZE(resp.xfer.data_buf)) + return -EINVAL; + for (i = 0; i < nmsgs; i++) { + if (msg[i].flags & I2C_M_RD) { + memcpy(msg[i].buf, p, msg[i].len); + p += msg[i].len; + } + } + + return 0; +} + +static int tegra186_bpmp_i2c_probe(struct udevice *dev) +{ + struct tegra186_bpmp_i2c *priv = dev_get_priv(dev); + int ret; + struct fdtdec_phandle_args args; + + ret = fdtdec_parse_phandle_with_args(gd->fdt_blob, dev->of_offset, + "nvidia,bpmp", NULL, 0, 0, &args); + if (ret < 0) { + debug("%s: fdtdec_parse_phandle_with_args() failed: %d\n", + __func__, ret); + return ret; + } + + priv->bpmp_bus_id = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, + "nvidia,bpmp-bus-id", U32_MAX); + if (priv->bpmp_bus_id == U32_MAX) { + debug("%s: could not parse nvidia,bpmp-bus-id\n", __func__); + return -ENODEV; + } + + return 0; +} + +static const struct dm_i2c_ops tegra186_bpmp_i2c_ops = { + .xfer = tegra186_bpmp_i2c_xfer, +}; + +static const struct udevice_id tegra186_bpmp_i2c_ids[] = { + { .compatible = "nvidia,tegra186-bpmp-i2c" }, + { } +}; + +U_BOOT_DRIVER(i2c_gpio) = { + .name = "tegra186_bpmp_i2c", + .id = UCLASS_I2C, + .of_match = tegra186_bpmp_i2c_ids, + .probe = tegra186_bpmp_i2c_probe, + .priv_auto_alloc_size = sizeof(struct tegra186_bpmp_i2c), + .ops = &tegra186_bpmp_i2c_ops, +}; diff --git a/drivers/i2c/tegra_i2c.c b/drivers/i2c/tegra_i2c.c index 2fa07f9c57..31ba263b72 100644 --- a/drivers/i2c/tegra_i2c.c +++ b/drivers/i2c/tegra_i2c.c @@ -12,13 +12,27 @@ #include <fdtdec.h> #include <i2c.h> #include <asm/io.h> +#ifdef CONFIG_TEGRA186 +#include <clk.h> +#include <reset.h> +#else #include <asm/arch/clock.h> #include <asm/arch/funcmux.h> -#include <asm/arch/gpio.h> #include <asm/arch/pinmux.h> #include <asm/arch-tegra/clk_rst.h> +#endif +#include <asm/arch/gpio.h> #include <asm/arch-tegra/tegra_i2c.h> +/* + * FIXME: TODO: This driver contains a number of ifdef CONFIG_TEGRA186 that + * should not be present. These are needed because newer Tegra SoCs support + * only the standard clock/reset APIs, whereas older Tegra SoCs support only + * a custom Tegra-specific API. ASAP the older Tegra SoCs' code should be + * fixed to implement the standard APIs, and all drivers converted to solely + * use the new standard APIs, with no ifdefs. + */ + DECLARE_GLOBAL_DATA_PTR; enum i2c_type { @@ -30,7 +44,12 @@ enum i2c_type { /* Information about i2c controller */ struct i2c_bus { int id; +#ifdef CONFIG_TEGRA186 + struct reset_ctl reset_ctl; + struct clk clk; +#else enum periph_id periph_id; +#endif int speed; int pinmux_config; struct i2c_control *control; @@ -62,12 +81,41 @@ static void set_packet_mode(struct i2c_bus *i2c_bus) static void i2c_reset_controller(struct i2c_bus *i2c_bus) { /* Reset I2C controller. */ +#ifdef CONFIG_TEGRA186 + reset_assert(&i2c_bus->reset_ctl); + udelay(1); + reset_deassert(&i2c_bus->reset_ctl); + udelay(1); +#else reset_periph(i2c_bus->periph_id, 1); +#endif /* re-program config register to packet mode */ set_packet_mode(i2c_bus); } +#ifdef CONFIG_TEGRA186 +static int i2c_init_clock(struct i2c_bus *i2c_bus, unsigned rate) +{ + int ret; + + ret = reset_assert(&i2c_bus->reset_ctl); + if (ret) + return ret; + ret = clk_enable(&i2c_bus->clk); + if (ret) + return ret; + ret = clk_set_rate(&i2c_bus->clk, rate); + if (IS_ERR_VALUE(ret)) + return ret; + ret = reset_deassert(&i2c_bus->reset_ctl); + if (ret) + return ret; + + return 0; +} +#endif + static void i2c_init_controller(struct i2c_bus *i2c_bus) { if (!i2c_bus->speed) @@ -78,8 +126,12 @@ static void i2c_init_controller(struct i2c_bus *i2c_bus) * here, in section 23.3.1, but in fact we seem to need a factor of * 16 to get the right frequency. */ +#ifdef CONFIG_TEGRA186 + i2c_init_clock(i2c_bus, i2c_bus->speed * 2 * 8); +#else clock_start_periph_pll(i2c_bus->periph_id, CLOCK_ID_PERIPH, i2c_bus->speed * 2 * 8); +#endif if (i2c_bus->type == TYPE_114) { /* @@ -94,12 +146,17 @@ static void i2c_init_controller(struct i2c_bus *i2c_bus) * is running, we hang, and we need it for the new calc. */ int clk_div_stdfst_mode = readl(&i2c_bus->regs->clk_div) >> 16; + unsigned rate = CLK_MULT_STD_FAST_MODE * + (clk_div_stdfst_mode + 1) * i2c_bus->speed * 2; debug("%s: CLK_DIV_STD_FAST_MODE setting = %d\n", __func__, clk_div_stdfst_mode); +#ifdef CONFIG_TEGRA186 + i2c_init_clock(i2c_bus, rate); +#else clock_start_periph_pll(i2c_bus->periph_id, CLOCK_ID_PERIPH, - CLK_MULT_STD_FAST_MODE * (clk_div_stdfst_mode + 1) * - i2c_bus->speed * 2); + rate); +#endif } /* Reset I2C controller. */ @@ -112,7 +169,9 @@ static void i2c_init_controller(struct i2c_bus *i2c_bus) setbits_le32(&dvc->ctrl3, DVC_CTRL_REG3_I2C_HW_SW_PROG_MASK); } +#ifndef CONFIG_TEGRA186 funcmux_select(i2c_bus->periph_id, i2c_bus->pinmux_config); +#endif } static void send_packet_headers( @@ -333,8 +392,12 @@ static int tegra_i2c_set_bus_speed(struct udevice *dev, unsigned int speed) static int tegra_i2c_probe(struct udevice *dev) { struct i2c_bus *i2c_bus = dev_get_priv(dev); +#ifdef CONFIG_TEGRA186 + int ret; +#else const void *blob = gd->fdt_blob; int node = dev->of_offset; +#endif bool is_dvc; i2c_bus->id = dev->seq; @@ -345,6 +408,18 @@ static int tegra_i2c_probe(struct udevice *dev) * We don't have a binding for pinmux yet. Leave it out for now. So * far no one needs anything other than the default. */ +#ifdef CONFIG_TEGRA186 + ret = reset_get_by_name(dev, "i2c", &i2c_bus->reset_ctl); + if (ret) { + error("reset_get_by_name() failed: %d\n", ret); + return ret; + } + ret = clk_get_by_name(dev, "i2c", &i2c_bus->clk); + if (ret) { + error("clk_get_by_name() failed: %d\n", ret); + return ret; + } +#else i2c_bus->pinmux_config = FUNCMUX_DEFAULT; i2c_bus->periph_id = clock_decode_periph_id(blob, node); @@ -359,6 +434,7 @@ static int tegra_i2c_probe(struct udevice *dev) */ if (i2c_bus->periph_id == -1) return -EINVAL; +#endif is_dvc = dev_get_driver_data(dev) == TYPE_DVC; if (is_dvc) { @@ -370,7 +446,12 @@ static int tegra_i2c_probe(struct udevice *dev) i2c_init_controller(i2c_bus); debug("%s: controller bus %d at %p, periph_id %d, speed %d: ", is_dvc ? "dvc" : "i2c", dev->seq, i2c_bus->regs, - i2c_bus->periph_id, i2c_bus->speed); +#ifndef CONFIG_TEGRA186 + i2c_bus->periph_id, +#else + -1, +#endif + i2c_bus->speed); return 0; } |