diff options
| -rw-r--r-- | drivers/bus/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/bus/Makefile | 1 | ||||
| -rw-r--r-- | drivers/bus/tegra-gmi.c | 284 | 
3 files changed, 292 insertions, 0 deletions
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 7010dcac9328..0c6da1efa216 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -149,6 +149,13 @@ config TEGRA_ACONNECT  	  Driver for the Tegra ACONNECT bus which is used to interface with  	  the devices inside the Audio Processing Engine (APE) for Tegra210. +config TEGRA_GMI +	tristate "Tegra Generic Memory Interface bus driver" +	depends on ARCH_TEGRA +	help +	  Driver for the Tegra Generic Memory Interface bus which can be used +	  to attach devices such as NOR, UART, FPGA and more. +  config UNIPHIER_SYSTEM_BUS  	tristate "UniPhier System Bus driver"  	depends on ARCH_UNIPHIER && OF diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index c6cfa6b2606e..a679d00fc4eb 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -19,5 +19,6 @@ obj-$(CONFIG_QCOM_EBI2)		+= qcom-ebi2.o  obj-$(CONFIG_SUNXI_RSB)		+= sunxi-rsb.o  obj-$(CONFIG_SIMPLE_PM_BUS)	+= simple-pm-bus.o  obj-$(CONFIG_TEGRA_ACONNECT)	+= tegra-aconnect.o +obj-$(CONFIG_TEGRA_GMI)		+= tegra-gmi.o  obj-$(CONFIG_UNIPHIER_SYSTEM_BUS)	+= uniphier-system-bus.o  obj-$(CONFIG_VEXPRESS_CONFIG)	+= vexpress-config.o diff --git a/drivers/bus/tegra-gmi.c b/drivers/bus/tegra-gmi.c new file mode 100644 index 000000000000..a6570789f7af --- /dev/null +++ b/drivers/bus/tegra-gmi.c @@ -0,0 +1,284 @@ +/* + * Driver for NVIDIA Generic Memory Interface + * + * Copyright (C) 2016 Host Mobility AB. All rights reserved. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/reset.h> + +#define TEGRA_GMI_CONFIG		0x00 +#define TEGRA_GMI_CONFIG_GO		BIT(31) +#define TEGRA_GMI_BUS_WIDTH_32BIT	BIT(30) +#define TEGRA_GMI_MUX_MODE		BIT(28) +#define TEGRA_GMI_RDY_BEFORE_DATA	BIT(24) +#define TEGRA_GMI_RDY_ACTIVE_HIGH	BIT(23) +#define TEGRA_GMI_ADV_ACTIVE_HIGH	BIT(22) +#define TEGRA_GMI_OE_ACTIVE_HIGH	BIT(21) +#define TEGRA_GMI_CS_ACTIVE_HIGH	BIT(20) +#define TEGRA_GMI_CS_SELECT(x)		((x & 0x7) << 4) + +#define TEGRA_GMI_TIMING0		0x10 +#define TEGRA_GMI_MUXED_WIDTH(x)	((x & 0xf) << 12) +#define TEGRA_GMI_HOLD_WIDTH(x)		((x & 0xf) << 8) +#define TEGRA_GMI_ADV_WIDTH(x)		((x & 0xf) << 4) +#define TEGRA_GMI_CE_WIDTH(x)		(x & 0xf) + +#define TEGRA_GMI_TIMING1		0x14 +#define TEGRA_GMI_WE_WIDTH(x)		((x & 0xff) << 16) +#define TEGRA_GMI_OE_WIDTH(x)		((x & 0xff) << 8) +#define TEGRA_GMI_WAIT_WIDTH(x)		(x & 0xff) + +#define TEGRA_GMI_MAX_CHIP_SELECT	8 + +struct tegra_gmi { +	struct device *dev; +	void __iomem *base; +	struct clk *clk; +	struct reset_control *rst; + +	u32 snor_config; +	u32 snor_timing0; +	u32 snor_timing1; +}; + +static int tegra_gmi_enable(struct tegra_gmi *gmi) +{ +	int err; + +	err = clk_prepare_enable(gmi->clk); +	if (err < 0) { +		dev_err(gmi->dev, "failed to enable clock: %d\n", err); +		return err; +	} + +	reset_control_assert(gmi->rst); +	usleep_range(2000, 4000); +	reset_control_deassert(gmi->rst); + +	writel(gmi->snor_timing0, gmi->base + TEGRA_GMI_TIMING0); +	writel(gmi->snor_timing1, gmi->base + TEGRA_GMI_TIMING1); + +	gmi->snor_config |= TEGRA_GMI_CONFIG_GO; +	writel(gmi->snor_config, gmi->base + TEGRA_GMI_CONFIG); + +	return 0; +} + +static void tegra_gmi_disable(struct tegra_gmi *gmi) +{ +	u32 config; + +	/* stop GMI operation */ +	config = readl(gmi->base + TEGRA_GMI_CONFIG); +	config &= ~TEGRA_GMI_CONFIG_GO; +	writel(config, gmi->base + TEGRA_GMI_CONFIG); + +	reset_control_assert(gmi->rst); +	clk_disable_unprepare(gmi->clk); +} + +static int tegra_gmi_parse_dt(struct tegra_gmi *gmi) +{ +	struct device_node *child; +	u32 property, ranges[4]; +	int err; + +	child = of_get_next_available_child(gmi->dev->of_node, NULL); +	if (!child) { +		dev_err(gmi->dev, "no child nodes found\n"); +		return -ENODEV; +	} + +	/* +	 * We currently only support one child device due to lack of +	 * chip-select address decoding. Which means that we only have one +	 * chip-select line from the GMI controller. +	 */ +	if (of_get_child_count(gmi->dev->of_node) > 1) +		dev_warn(gmi->dev, "only one child device is supported."); + +	if (of_property_read_bool(child, "nvidia,snor-data-width-32bit")) +		gmi->snor_config |= TEGRA_GMI_BUS_WIDTH_32BIT; + +	if (of_property_read_bool(child, "nvidia,snor-mux-mode")) +		gmi->snor_config |= TEGRA_GMI_MUX_MODE; + +	if (of_property_read_bool(child, "nvidia,snor-rdy-active-before-data")) +		gmi->snor_config |= TEGRA_GMI_RDY_BEFORE_DATA; + +	if (of_property_read_bool(child, "nvidia,snor-rdy-active-high")) +		gmi->snor_config |= TEGRA_GMI_RDY_ACTIVE_HIGH; + +	if (of_property_read_bool(child, "nvidia,snor-adv-active-high")) +		gmi->snor_config |= TEGRA_GMI_ADV_ACTIVE_HIGH; + +	if (of_property_read_bool(child, "nvidia,snor-oe-active-high")) +		gmi->snor_config |= TEGRA_GMI_OE_ACTIVE_HIGH; + +	if (of_property_read_bool(child, "nvidia,snor-cs-active-high")) +		gmi->snor_config |= TEGRA_GMI_CS_ACTIVE_HIGH; + +	/* Decode the CS# */ +	err = of_property_read_u32_array(child, "ranges", ranges, 4); +	if (err < 0) { +		/* Invalid binding */ +		if (err == -EOVERFLOW) { +			dev_err(gmi->dev, +				"failed to decode CS: invalid ranges length\n"); +			goto error_cs; +		} + +		/* +		 * If we reach here it means that the child node has an empty +		 * ranges or it does not exist at all. Attempt to decode the +		 * CS# from the reg property instead. +		 */ +		err = of_property_read_u32(child, "reg", &property); +		if (err < 0) { +			dev_err(gmi->dev, +				"failed to decode CS: no reg property found\n"); +			goto error_cs; +		} +	} else { +		property = ranges[1]; +	} + +	/* Valid chip selects are CS0-CS7 */ +	if (property >= TEGRA_GMI_MAX_CHIP_SELECT) { +		dev_err(gmi->dev, "invalid chip select: %d", property); +		err = -EINVAL; +		goto error_cs; +	} + +	gmi->snor_config |= TEGRA_GMI_CS_SELECT(property); + +	/* The default values that are provided below are reset values */ +	if (!of_property_read_u32(child, "nvidia,snor-muxed-width", &property)) +		gmi->snor_timing0 |= TEGRA_GMI_MUXED_WIDTH(property); +	else +		gmi->snor_timing0 |= TEGRA_GMI_MUXED_WIDTH(1); + +	if (!of_property_read_u32(child, "nvidia,snor-hold-width", &property)) +		gmi->snor_timing0 |= TEGRA_GMI_HOLD_WIDTH(property); +	else +		gmi->snor_timing0 |= TEGRA_GMI_HOLD_WIDTH(1); + +	if (!of_property_read_u32(child, "nvidia,snor-adv-width", &property)) +		gmi->snor_timing0 |= TEGRA_GMI_ADV_WIDTH(property); +	else +		gmi->snor_timing0 |= TEGRA_GMI_ADV_WIDTH(1); + +	if (!of_property_read_u32(child, "nvidia,snor-ce-width", &property)) +		gmi->snor_timing0 |= TEGRA_GMI_CE_WIDTH(property); +	else +		gmi->snor_timing0 |= TEGRA_GMI_CE_WIDTH(4); + +	if (!of_property_read_u32(child, "nvidia,snor-we-width", &property)) +		gmi->snor_timing1 |= TEGRA_GMI_WE_WIDTH(property); +	else +		gmi->snor_timing1 |= TEGRA_GMI_WE_WIDTH(1); + +	if (!of_property_read_u32(child, "nvidia,snor-oe-width", &property)) +		gmi->snor_timing1 |= TEGRA_GMI_OE_WIDTH(property); +	else +		gmi->snor_timing1 |= TEGRA_GMI_OE_WIDTH(1); + +	if (!of_property_read_u32(child, "nvidia,snor-wait-width", &property)) +		gmi->snor_timing1 |= TEGRA_GMI_WAIT_WIDTH(property); +	else +		gmi->snor_timing1 |= TEGRA_GMI_WAIT_WIDTH(3); + +error_cs: +	of_node_put(child); +	return err; +} + +static int tegra_gmi_probe(struct platform_device *pdev) +{ +	struct device *dev = &pdev->dev; +	struct tegra_gmi *gmi; +	struct resource *res; +	int err; + +	gmi = devm_kzalloc(dev, sizeof(*gmi), GFP_KERNEL); +	if (!gmi) +		return -ENOMEM; + +	gmi->dev = dev; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	gmi->base = devm_ioremap_resource(dev, res); +	if (IS_ERR(gmi->base)) +		return PTR_ERR(gmi->base); + +	gmi->clk = devm_clk_get(dev, "gmi"); +	if (IS_ERR(gmi->clk)) { +		dev_err(dev, "can not get clock\n"); +		return PTR_ERR(gmi->clk); +	} + +	gmi->rst = devm_reset_control_get(dev, "gmi"); +	if (IS_ERR(gmi->rst)) { +		dev_err(dev, "can not get reset\n"); +		return PTR_ERR(gmi->rst); +	} + +	err = tegra_gmi_parse_dt(gmi); +	if (err) +		return err; + +	err = tegra_gmi_enable(gmi); +	if (err < 0) +		return err; + +	err = of_platform_default_populate(dev->of_node, NULL, dev); +	if (err < 0) { +		dev_err(dev, "fail to create devices.\n"); +		tegra_gmi_disable(gmi); +		return err; +	} + +	platform_set_drvdata(pdev, gmi); + +	return 0; +} + +static int tegra_gmi_remove(struct platform_device *pdev) +{ +	struct tegra_gmi *gmi = platform_get_drvdata(pdev); + +	of_platform_depopulate(gmi->dev); +	tegra_gmi_disable(gmi); + +	return 0; +} + +static const struct of_device_id tegra_gmi_id_table[] = { +	{ .compatible = "nvidia,tegra20-gmi", }, +	{ .compatible = "nvidia,tegra30-gmi", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, tegra_gmi_id_table); + +static struct platform_driver tegra_gmi_driver = { +	.probe = tegra_gmi_probe, +	.remove = tegra_gmi_remove, +	.driver = { +		.name		= "tegra-gmi", +		.of_match_table	= tegra_gmi_id_table, +	}, +}; +module_platform_driver(tegra_gmi_driver); + +MODULE_AUTHOR("Mirza Krak <mirza.krak@gmail.com"); +MODULE_DESCRIPTION("NVIDIA Tegra GMI Bus Driver"); +MODULE_LICENSE("GPL v2");  | 
