diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-10-01 18:46:13 -0700 | 
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-10-01 18:46:13 -0700 | 
| commit | 8f446a7a069e0af0639385f67c78ee2279bca04c (patch) | |
| tree | 580cf495616b36ca0af0826afa87c430cdc1e7cb /drivers | |
| parent | 84be4ae2c038e2b03d650cbf2a7cfd9e8d6e9e51 (diff) | |
| parent | 04ef037c926ddb31088c976538e29eada4fd1490 (diff) | |
| download | olio-linux-3.10-8f446a7a069e0af0639385f67c78ee2279bca04c.tar.xz olio-linux-3.10-8f446a7a069e0af0639385f67c78ee2279bca04c.zip  | |
Merge tag 'drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM soc driver specific changes from Olof Johansson:
 - A long-coming conversion of various platforms to a common LED
   infrastructure
 - AT91 is moved over to use the newer MCI driver for MMC
 - Pincontrol conversions for samsung platforms
 - DT bindings for gscaler on samsung
 - i2c driver fixes for tegra, acked by i2c maintainer
Fix up conflicts as per Olof.
* tag 'drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (48 commits)
  drivers: bus: omap_l3: use resources instead of hardcoded irqs
  pinctrl: exynos: Fix wakeup IRQ domain registration check
  pinctrl: samsung: Uninline samsung_pinctrl_get_soc_data
  pinctrl: exynos: Correct the detection of wakeup-eint node
  pinctrl: exynos: Mark exynos_irq_demux_eint as inline
  pinctrl: exynos: Handle only unmasked wakeup interrupts
  pinctrl: exynos: Fix typos in gpio/wkup _irq_mask
  pinctrl: exynos: Set pin function to EINT in irq_set_type of GPIO EINTa
  drivers: bus: Move the OMAP interconnect driver to drivers/bus/
  i2c: tegra: dynamically control fast clk
  i2c: tegra: I2_M_NOSTART functionality not supported in Tegra20
  ARM: tegra: clock: remove unused clock entry for i2c
  ARM: tegra: clock: add connection name in i2c clock entry
  i2c: tegra: pass proper name for getting clock
  ARM: tegra: clock: add i2c fast clock entry in clock table
  ARM: EXYNOS: Adds G-Scaler device from Device Tree
  ARM: EXYNOS: Add clock support for G-Scaler
  ARM: EXYNOS: Enable pinctrl driver support for EXYNOS4 device tree enabled platform
  ARM: dts: Add pinctrl node entries for SAMSUNG EXYNOS4210 SoC
  ARM: EXYNOS: skip wakeup interrupt setup if pinctrl driver is used
  ...
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/Makefile | 1 | ||||
| -rw-r--r-- | drivers/bus/Kconfig | 21 | ||||
| -rw-r--r-- | drivers/bus/Makefile | 8 | ||||
| -rw-r--r-- | drivers/bus/omap-ocp2scp.c | 88 | ||||
| -rw-r--r-- | drivers/bus/omap_l3_noc.c | 267 | ||||
| -rw-r--r-- | drivers/bus/omap_l3_noc.h | 176 | ||||
| -rw-r--r-- | drivers/bus/omap_l3_smx.c | 297 | ||||
| -rw-r--r-- | drivers/bus/omap_l3_smx.h | 338 | ||||
| -rw-r--r-- | drivers/char/nwflash.c | 34 | ||||
| -rw-r--r-- | drivers/gpio/gpio-samsung.c | 21 | ||||
| -rw-r--r-- | drivers/i2c/busses/i2c-tegra.c | 130 | ||||
| -rw-r--r-- | drivers/leds/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/leds/Makefile | 1 | ||||
| -rw-r--r-- | drivers/leds/ledtrig-cpu.c | 163 | ||||
| -rw-r--r-- | drivers/mtd/nand/Kconfig | 40 | ||||
| -rw-r--r-- | drivers/pinctrl/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/pinctrl/Makefile | 2 | ||||
| -rw-r--r-- | drivers/pinctrl/pinctrl-exynos.c | 579 | ||||
| -rw-r--r-- | drivers/pinctrl/pinctrl-exynos.h | 218 | ||||
| -rw-r--r-- | drivers/pinctrl/pinctrl-samsung.c | 888 | ||||
| -rw-r--r-- | drivers/pinctrl/pinctrl-samsung.h | 239 | 
22 files changed, 3420 insertions, 112 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index 36d3daa19a7..dbdefa3fe77 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -2,6 +2,8 @@ menu "Device Drivers"  source "drivers/base/Kconfig" +source "drivers/bus/Kconfig" +  source "drivers/connector/Kconfig"  source "drivers/mtd/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 8c30e73cd94..acb48fa4531 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -6,6 +6,7 @@  #  obj-y				+= irqchip/ +obj-y				+= bus/  # GPIO must come after pinctrl as gpios may need to mux pins etc  obj-y				+= pinctrl/ diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig new file mode 100644 index 00000000000..bbec35d21fe --- /dev/null +++ b/drivers/bus/Kconfig @@ -0,0 +1,21 @@ +# +# Bus Devices +# + +menu "Bus devices" + +config OMAP_OCP2SCP +	tristate "OMAP OCP2SCP DRIVER" +	help +	  Driver to enable ocp2scp module which transforms ocp interface +	  protocol to scp protocol. In OMAP4, USB PHY is connected via +	  OCP2SCP and in OMAP5, both USB PHY and SATA PHY is connected via +	  OCP2SCP. + +config OMAP_INTERCONNECT +	tristate "OMAP INTERCONNECT DRIVER" +	depends on ARCH_OMAP2PLUS + +	help +	  Driver to enable OMAP interconnect error handling driver. +endmenu diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile new file mode 100644 index 00000000000..45d997c8545 --- /dev/null +++ b/drivers/bus/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the bus drivers. +# + +obj-$(CONFIG_OMAP_OCP2SCP)	+= omap-ocp2scp.o + +# Interconnect bus driver for OMAP SoCs. +obj-$(CONFIG_OMAP_INTERCONNECT)	+= omap_l3_smx.o omap_l3_noc.o diff --git a/drivers/bus/omap-ocp2scp.c b/drivers/bus/omap-ocp2scp.c new file mode 100644 index 00000000000..ff63560b846 --- /dev/null +++ b/drivers/bus/omap-ocp2scp.c @@ -0,0 +1,88 @@ +/* + * omap-ocp2scp.c - transform ocp interface protocol to scp protocol + * + * Copyright (C) 2012 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; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_platform.h> + +static int ocp2scp_remove_devices(struct device *dev, void *c) +{ +	struct platform_device *pdev = to_platform_device(dev); + +	platform_device_unregister(pdev); + +	return 0; +} + +static int __devinit omap_ocp2scp_probe(struct platform_device *pdev) +{ +	int			ret; +	struct device_node	*np = pdev->dev.of_node; + +	if (np) { +		ret = of_platform_populate(np, NULL, NULL, &pdev->dev); +		if (ret) { +			dev_err(&pdev->dev, "failed to add resources for ocp2scp child\n"); +			goto err0; +		} +	} +	pm_runtime_enable(&pdev->dev); + +	return 0; + +err0: +	device_for_each_child(&pdev->dev, NULL, ocp2scp_remove_devices); + +	return ret; +} + +static int __devexit omap_ocp2scp_remove(struct platform_device *pdev) +{ +	pm_runtime_disable(&pdev->dev); +	device_for_each_child(&pdev->dev, NULL, ocp2scp_remove_devices); + +	return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id omap_ocp2scp_id_table[] = { +	{ .compatible = "ti,omap-ocp2scp" }, +	{} +}; +MODULE_DEVICE_TABLE(of, omap_ocp2scp_id_table); +#endif + +static struct platform_driver omap_ocp2scp_driver = { +	.probe		= omap_ocp2scp_probe, +	.remove		= __devexit_p(omap_ocp2scp_remove), +	.driver		= { +		.name	= "omap-ocp2scp", +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(omap_ocp2scp_id_table), +	}, +}; + +module_platform_driver(omap_ocp2scp_driver); + +MODULE_ALIAS("platform: omap-ocp2scp"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP OCP2SCP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/bus/omap_l3_noc.c b/drivers/bus/omap_l3_noc.c new file mode 100644 index 00000000000..44b2b3e5788 --- /dev/null +++ b/drivers/bus/omap_l3_noc.c @@ -0,0 +1,267 @@ +/* + * OMAP4XXX L3 Interconnect error handling driver + * + * Copyright (C) 2011 Texas Corporation + *	Santosh Shilimkar <santosh.shilimkar@ti.com> + *	Sricharan <r.sricharan@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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/slab.h> + +#include "soc.h" +#include "omap_l3_noc.h" + +/* + * Interrupt Handler for L3 error detection. + *	1) Identify the L3 clockdomain partition to which the error belongs to. + *	2) Identify the slave where the error information is logged + *	3) Print the logged information. + *	4) Add dump stack to provide kernel trace. + * + * Two Types of errors : + *	1) Custom errors in L3 : + *		Target like DMM/FW/EMIF generates SRESP=ERR error + *	2) Standard L3 error: + *		- Unsupported CMD. + *			L3 tries to access target while it is idle + *		- OCP disconnect. + *		- Address hole error: + *			If DSS/ISS/FDIF/USBHOSTFS access a target where they + *			do not have connectivity, the error is logged in + *			their default target which is DMM2. + * + *	On High Secure devices, firewall errors are possible and those + *	can be trapped as well. But the trapping is implemented as part + *	secure software and hence need not be implemented here. + */ +static irqreturn_t l3_interrupt_handler(int irq, void *_l3) +{ + +	struct omap4_l3 *l3 = _l3; +	int inttype, i, k; +	int err_src = 0; +	u32 std_err_main, err_reg, clear, masterid; +	void __iomem *base, *l3_targ_base; +	char *target_name, *master_name = "UN IDENTIFIED"; + +	/* Get the Type of interrupt */ +	inttype = irq == l3->app_irq ? L3_APPLICATION_ERROR : L3_DEBUG_ERROR; + +	for (i = 0; i < L3_MODULES; i++) { +		/* +		 * Read the regerr register of the clock domain +		 * to determine the source +		 */ +		base = l3->l3_base[i]; +		err_reg = __raw_readl(base + l3_flagmux[i] + +					+ L3_FLAGMUX_REGERR0 + (inttype << 3)); + +		/* Get the corresponding error and analyse */ +		if (err_reg) { +			/* Identify the source from control status register */ +			err_src = __ffs(err_reg); + +			/* Read the stderrlog_main_source from clk domain */ +			l3_targ_base = base + *(l3_targ[i] + err_src); +			std_err_main =  __raw_readl(l3_targ_base + +					L3_TARG_STDERRLOG_MAIN); +			masterid = __raw_readl(l3_targ_base + +					L3_TARG_STDERRLOG_MSTADDR); + +			switch (std_err_main & CUSTOM_ERROR) { +			case STANDARD_ERROR: +				target_name = +					l3_targ_inst_name[i][err_src]; +				WARN(true, "L3 standard error: TARGET:%s at address 0x%x\n", +					target_name, +					__raw_readl(l3_targ_base + +						L3_TARG_STDERRLOG_SLVOFSLSB)); +				/* clear the std error log*/ +				clear = std_err_main | CLEAR_STDERR_LOG; +				writel(clear, l3_targ_base + +					L3_TARG_STDERRLOG_MAIN); +				break; + +			case CUSTOM_ERROR: +				target_name = +					l3_targ_inst_name[i][err_src]; +				for (k = 0; k < NUM_OF_L3_MASTERS; k++) { +					if (masterid == l3_masters[k].id) +						master_name = +							l3_masters[k].name; +				} +				WARN(true, "L3 custom error: MASTER:%s TARGET:%s\n", +					master_name, target_name); +				/* clear the std error log*/ +				clear = std_err_main | CLEAR_STDERR_LOG; +				writel(clear, l3_targ_base + +					L3_TARG_STDERRLOG_MAIN); +				break; + +			default: +				/* Nothing to be handled here as of now */ +				break; +			} +		/* Error found so break the for loop */ +		break; +		} +	} +	return IRQ_HANDLED; +} + +static int __devinit omap4_l3_probe(struct platform_device *pdev) +{ +	static struct omap4_l3 *l3; +	struct resource	*res; +	int ret; + +	l3 = kzalloc(sizeof(*l3), GFP_KERNEL); +	if (!l3) +		return -ENOMEM; + +	platform_set_drvdata(pdev, l3); +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		dev_err(&pdev->dev, "couldn't find resource 0\n"); +		ret = -ENODEV; +		goto err0; +	} + +	l3->l3_base[0] = ioremap(res->start, resource_size(res)); +	if (!l3->l3_base[0]) { +		dev_err(&pdev->dev, "ioremap failed\n"); +		ret = -ENOMEM; +		goto err0; +	} + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1); +	if (!res) { +		dev_err(&pdev->dev, "couldn't find resource 1\n"); +		ret = -ENODEV; +		goto err1; +	} + +	l3->l3_base[1] = ioremap(res->start, resource_size(res)); +	if (!l3->l3_base[1]) { +		dev_err(&pdev->dev, "ioremap failed\n"); +		ret = -ENOMEM; +		goto err1; +	} + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 2); +	if (!res) { +		dev_err(&pdev->dev, "couldn't find resource 2\n"); +		ret = -ENODEV; +		goto err2; +	} + +	l3->l3_base[2] = ioremap(res->start, resource_size(res)); +	if (!l3->l3_base[2]) { +		dev_err(&pdev->dev, "ioremap failed\n"); +		ret = -ENOMEM; +		goto err2; +	} + +	/* +	 * Setup interrupt Handlers +	 */ +	l3->debug_irq = platform_get_irq(pdev, 0); +	ret = request_irq(l3->debug_irq, +			l3_interrupt_handler, +			IRQF_DISABLED, "l3-dbg-irq", l3); +	if (ret) { +		pr_crit("L3: request_irq failed to register for 0x%x\n", +						l3->debug_irq); +		goto err3; +	} + +	l3->app_irq = platform_get_irq(pdev, 1); +	ret = request_irq(l3->app_irq, +			l3_interrupt_handler, +			IRQF_DISABLED, "l3-app-irq", l3); +	if (ret) { +		pr_crit("L3: request_irq failed to register for 0x%x\n", +						l3->app_irq); +		goto err4; +	} + +	return 0; + +err4: +	free_irq(l3->debug_irq, l3); +err3: +	iounmap(l3->l3_base[2]); +err2: +	iounmap(l3->l3_base[1]); +err1: +	iounmap(l3->l3_base[0]); +err0: +	kfree(l3); +	return ret; +} + +static int __devexit omap4_l3_remove(struct platform_device *pdev) +{ +	struct omap4_l3 *l3 = platform_get_drvdata(pdev); + +	free_irq(l3->app_irq, l3); +	free_irq(l3->debug_irq, l3); +	iounmap(l3->l3_base[0]); +	iounmap(l3->l3_base[1]); +	iounmap(l3->l3_base[2]); +	kfree(l3); + +	return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id l3_noc_match[] = { +	{.compatible = "ti,omap4-l3-noc", }, +	{}, +}; +MODULE_DEVICE_TABLE(of, l3_noc_match); +#else +#define l3_noc_match NULL +#endif + +static struct platform_driver omap4_l3_driver = { +	.probe		= omap4_l3_probe, +	.remove		= __devexit_p(omap4_l3_remove), +	.driver		= { +		.name		= "omap_l3_noc", +		.owner		= THIS_MODULE, +		.of_match_table = l3_noc_match, +	}, +}; + +static int __init omap4_l3_init(void) +{ +	return platform_driver_register(&omap4_l3_driver); +} +postcore_initcall_sync(omap4_l3_init); + +static void __exit omap4_l3_exit(void) +{ +	platform_driver_unregister(&omap4_l3_driver); +} +module_exit(omap4_l3_exit); diff --git a/drivers/bus/omap_l3_noc.h b/drivers/bus/omap_l3_noc.h new file mode 100644 index 00000000000..a6ce34dc481 --- /dev/null +++ b/drivers/bus/omap_l3_noc.h @@ -0,0 +1,176 @@ +/* + * OMAP4XXX L3 Interconnect  error handling driver header + * + * Copyright (C) 2011 Texas Corporation + *	Santosh Shilimkar <santosh.shilimkar@ti.com> + *	sricharan <r.sricharan@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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +#ifndef __ARCH_ARM_MACH_OMAP2_L3_INTERCONNECT_3XXX_H +#define __ARCH_ARM_MACH_OMAP2_L3_INTERCONNECT_3XXX_H + +#define L3_MODULES			3 +#define CLEAR_STDERR_LOG		(1 << 31) +#define CUSTOM_ERROR			0x2 +#define STANDARD_ERROR			0x0 +#define INBAND_ERROR			0x0 +#define L3_APPLICATION_ERROR		0x0 +#define L3_DEBUG_ERROR			0x1 + +/* L3 TARG register offsets */ +#define L3_TARG_STDERRLOG_MAIN		0x48 +#define L3_TARG_STDERRLOG_SLVOFSLSB	0x5c +#define L3_TARG_STDERRLOG_MSTADDR	0x68 +#define L3_FLAGMUX_REGERR0		0xc + +#define NUM_OF_L3_MASTERS	(sizeof(l3_masters)/sizeof(l3_masters[0])) + +static u32 l3_flagmux[L3_MODULES] = { +	0x500, +	0x1000, +	0X0200 +}; + +/* L3 Target standard Error register offsets */ +static u32 l3_targ_inst_clk1[] = { +	0x100, /* DMM1 */ +	0x200, /* DMM2 */ +	0x300, /* ABE */ +	0x400, /* L4CFG */ +	0x600,  /* CLK2 PWR DISC */ +	0x0,	/* Host CLK1 */ +	0x900	/* L4 Wakeup */ +}; + +static u32 l3_targ_inst_clk2[] = { +	0x500, /* CORTEX M3 */ +	0x300, /* DSS */ +	0x100, /* GPMC */ +	0x400, /* ISS */ +	0x700, /* IVAHD */ +	0xD00, /* missing in TRM  corresponds to AES1*/ +	0x900, /* L4 PER0*/ +	0x200, /* OCMRAM */ +	0x100, /* missing in TRM corresponds to GPMC sERROR*/ +	0x600, /* SGX */ +	0x800, /* SL2 */ +	0x1600, /* C2C */ +	0x1100,	/* missing in TRM corresponds PWR DISC CLK1*/ +	0xF00, /* missing in TRM corrsponds to SHA1*/ +	0xE00, /* missing in TRM corresponds to AES2*/ +	0xC00, /* L4 PER3 */ +	0xA00, /* L4 PER1*/ +	0xB00, /* L4 PER2*/ +	0x0, /* HOST CLK2 */ +	0x1800, /* CAL */ +	0x1700 /* LLI */ +}; + +static u32 l3_targ_inst_clk3[] = { +	0x0100	/* EMUSS */, +	0x0300, /* DEBUGSS_CT_TBR */ +	0x0 /* HOST CLK3 */ +}; + +static struct l3_masters_data { +	u32 id; +	char name[10]; +} l3_masters[] = { +	{ 0x0 , "MPU"}, +	{ 0x10, "CS_ADP"}, +	{ 0x14, "xxx"}, +	{ 0x20, "DSP"}, +	{ 0x30, "IVAHD"}, +	{ 0x40, "ISS"}, +	{ 0x44, "DucatiM3"}, +	{ 0x48, "FaceDetect"}, +	{ 0x50, "SDMA_Rd"}, +	{ 0x54, "SDMA_Wr"}, +	{ 0x58, "xxx"}, +	{ 0x5C, "xxx"}, +	{ 0x60, "SGX"}, +	{ 0x70, "DSS"}, +	{ 0x80, "C2C"}, +	{ 0x88, "xxx"}, +	{ 0x8C, "xxx"}, +	{ 0x90, "HSI"}, +	{ 0xA0, "MMC1"}, +	{ 0xA4, "MMC2"}, +	{ 0xA8, "MMC6"}, +	{ 0xB0, "UNIPRO1"}, +	{ 0xC0, "USBHOSTHS"}, +	{ 0xC4, "USBOTGHS"}, +	{ 0xC8, "USBHOSTFS"} +}; + +static char *l3_targ_inst_name[L3_MODULES][21] = { +	{ +		"DMM1", +		"DMM2", +		"ABE", +		"L4CFG", +		"CLK2 PWR DISC", +		"HOST CLK1", +		"L4 WAKEUP" +	}, +	{ +		"CORTEX M3" , +		"DSS ", +		"GPMC ", +		"ISS ", +		"IVAHD ", +		"AES1", +		"L4 PER0", +		"OCMRAM ", +		"GPMC sERROR", +		"SGX ", +		"SL2 ", +		"C2C ", +		"PWR DISC CLK1", +		"SHA1", +		"AES2", +		"L4 PER3", +		"L4 PER1", +		"L4 PER2", +		"HOST CLK2", +		"CAL", +		"LLI" +	}, +	{ +		"EMUSS", +		"DEBUG SOURCE", +		"HOST CLK3" +	}, +}; + +static u32 *l3_targ[L3_MODULES] = { +	l3_targ_inst_clk1, +	l3_targ_inst_clk2, +	l3_targ_inst_clk3, +}; + +struct omap4_l3 { +	struct device *dev; +	struct clk *ick; + +	/* memory base */ +	void __iomem *l3_base[L3_MODULES]; + +	int debug_irq; +	int app_irq; +}; +#endif diff --git a/drivers/bus/omap_l3_smx.c b/drivers/bus/omap_l3_smx.c new file mode 100644 index 00000000000..acc216491b8 --- /dev/null +++ b/drivers/bus/omap_l3_smx.c @@ -0,0 +1,297 @@ +/* + * OMAP3XXX L3 Interconnect Driver + * + * Copyright (C) 2011 Texas Corporation + *	Felipe Balbi <balbi@ti.com> + *	Santosh Shilimkar <santosh.shilimkar@ti.com> + *	Sricharan <r.sricharan@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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include "omap_l3_smx.h" + +static inline u64 omap3_l3_readll(void __iomem *base, u16 reg) +{ +	return __raw_readll(base + reg); +} + +static inline void omap3_l3_writell(void __iomem *base, u16 reg, u64 value) +{ +	__raw_writell(value, base + reg); +} + +static inline enum omap3_l3_code omap3_l3_decode_error_code(u64 error) +{ +	return (error & 0x0f000000) >> L3_ERROR_LOG_CODE; +} + +static inline u32 omap3_l3_decode_addr(u64 error_addr) +{ +	return error_addr & 0xffffffff; +} + +static inline unsigned omap3_l3_decode_cmd(u64 error) +{ +	return (error & 0x07) >> L3_ERROR_LOG_CMD; +} + +static inline enum omap3_l3_initiator_id omap3_l3_decode_initid(u64 error) +{ +	return (error & 0xff00) >> L3_ERROR_LOG_INITID; +} + +static inline unsigned omap3_l3_decode_req_info(u64 error) +{ +	return (error >> 32) & 0xffff; +} + +static char *omap3_l3_code_string(u8 code) +{ +	switch (code) { +	case OMAP_L3_CODE_NOERROR: +		return "No Error"; +	case OMAP_L3_CODE_UNSUP_CMD: +		return "Unsupported Command"; +	case OMAP_L3_CODE_ADDR_HOLE: +		return "Address Hole"; +	case OMAP_L3_CODE_PROTECT_VIOLATION: +		return "Protection Violation"; +	case OMAP_L3_CODE_IN_BAND_ERR: +		return "In-band Error"; +	case OMAP_L3_CODE_REQ_TOUT_NOT_ACCEPT: +		return "Request Timeout Not Accepted"; +	case OMAP_L3_CODE_REQ_TOUT_NO_RESP: +		return "Request Timeout, no response"; +	default: +		return "UNKNOWN error"; +	} +} + +static char *omap3_l3_initiator_string(u8 initid) +{ +	switch (initid) { +	case OMAP_L3_LCD: +		return "LCD"; +	case OMAP_L3_SAD2D: +		return "SAD2D"; +	case OMAP_L3_IA_MPU_SS_1: +	case OMAP_L3_IA_MPU_SS_2: +	case OMAP_L3_IA_MPU_SS_3: +	case OMAP_L3_IA_MPU_SS_4: +	case OMAP_L3_IA_MPU_SS_5: +		return "MPU"; +	case OMAP_L3_IA_IVA_SS_1: +	case OMAP_L3_IA_IVA_SS_2: +	case OMAP_L3_IA_IVA_SS_3: +		return "IVA_SS"; +	case OMAP_L3_IA_IVA_SS_DMA_1: +	case OMAP_L3_IA_IVA_SS_DMA_2: +	case OMAP_L3_IA_IVA_SS_DMA_3: +	case OMAP_L3_IA_IVA_SS_DMA_4: +	case OMAP_L3_IA_IVA_SS_DMA_5: +	case OMAP_L3_IA_IVA_SS_DMA_6: +		return "IVA_SS_DMA"; +	case OMAP_L3_IA_SGX: +		return "SGX"; +	case OMAP_L3_IA_CAM_1: +	case OMAP_L3_IA_CAM_2: +	case OMAP_L3_IA_CAM_3: +		return "CAM"; +	case OMAP_L3_IA_DAP: +		return "DAP"; +	case OMAP_L3_SDMA_WR_1: +	case OMAP_L3_SDMA_WR_2: +		return "SDMA_WR"; +	case OMAP_L3_SDMA_RD_1: +	case OMAP_L3_SDMA_RD_2: +	case OMAP_L3_SDMA_RD_3: +	case OMAP_L3_SDMA_RD_4: +		return "SDMA_RD"; +	case OMAP_L3_USBOTG: +		return "USB_OTG"; +	case OMAP_L3_USBHOST: +		return "USB_HOST"; +	default: +		return "UNKNOWN Initiator"; +	} +} + +/* + * omap3_l3_block_irq - handles a register block's irq + * @l3: struct omap3_l3 * + * @base: register block base address + * @error: L3_ERROR_LOG register of our block + * + * Called in hard-irq context. Caller should take care of locking + * + * OMAP36xx TRM gives, on page 2001, Figure 9-10, the Typical Error + * Analysis Sequence, we are following that sequence here, please + * refer to that Figure for more information on the subject. + */ +static irqreturn_t omap3_l3_block_irq(struct omap3_l3 *l3, +					u64 error, int error_addr) +{ +	u8 code = omap3_l3_decode_error_code(error); +	u8 initid = omap3_l3_decode_initid(error); +	u8 multi = error & L3_ERROR_LOG_MULTI; +	u32 address = omap3_l3_decode_addr(error_addr); + +	pr_err("%s seen by %s %s at address %x\n", +			omap3_l3_code_string(code), +			omap3_l3_initiator_string(initid), +			multi ? "Multiple Errors" : "", address); +	WARN_ON(1); + +	return IRQ_HANDLED; +} + +static irqreturn_t omap3_l3_app_irq(int irq, void *_l3) +{ +	struct omap3_l3 *l3 = _l3; +	u64 status, clear; +	u64 error; +	u64 error_addr; +	u64 err_source = 0; +	void __iomem *base; +	int int_type; +	irqreturn_t ret = IRQ_NONE; + +	int_type = irq == l3->app_irq ? L3_APPLICATION_ERROR : L3_DEBUG_ERROR; +	if (!int_type) { +		status = omap3_l3_readll(l3->rt, L3_SI_FLAG_STATUS_0); +		/* +		 * if we have a timeout error, there's nothing we can +		 * do besides rebooting the board. So let's BUG on any +		 * of such errors and handle the others. timeout error +		 * is severe and not expected to occur. +		 */ +		BUG_ON(status & L3_STATUS_0_TIMEOUT_MASK); +	} else { +		status = omap3_l3_readll(l3->rt, L3_SI_FLAG_STATUS_1); +		/* No timeout error for debug sources */ +	} + +	/* identify the error source */ +	err_source = __ffs(status); + +	base = l3->rt + omap3_l3_bases[int_type][err_source]; +	error = omap3_l3_readll(base, L3_ERROR_LOG); +	if (error) { +		error_addr = omap3_l3_readll(base, L3_ERROR_LOG_ADDR); +		ret |= omap3_l3_block_irq(l3, error, error_addr); +	} + +	/* Clear the status register */ +	clear = (L3_AGENT_STATUS_CLEAR_IA << int_type) | +		L3_AGENT_STATUS_CLEAR_TA; +	omap3_l3_writell(base, L3_AGENT_STATUS, clear); + +	/* clear the error log register */ +	omap3_l3_writell(base, L3_ERROR_LOG, error); + +	return ret; +} + +static int __init omap3_l3_probe(struct platform_device *pdev) +{ +	struct omap3_l3 *l3; +	struct resource *res; +	int ret; + +	l3 = kzalloc(sizeof(*l3), GFP_KERNEL); +	if (!l3) +		return -ENOMEM; + +	platform_set_drvdata(pdev, l3); + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		dev_err(&pdev->dev, "couldn't find resource\n"); +		ret = -ENODEV; +		goto err0; +	} +	l3->rt = ioremap(res->start, resource_size(res)); +	if (!l3->rt) { +		dev_err(&pdev->dev, "ioremap failed\n"); +		ret = -ENOMEM; +		goto err0; +	} + +	l3->debug_irq = platform_get_irq(pdev, 0); +	ret = request_irq(l3->debug_irq, omap3_l3_app_irq, +		IRQF_DISABLED | IRQF_TRIGGER_RISING, +		"l3-debug-irq", l3); +	if (ret) { +		dev_err(&pdev->dev, "couldn't request debug irq\n"); +		goto err1; +	} + +	l3->app_irq = platform_get_irq(pdev, 1); +	ret = request_irq(l3->app_irq, omap3_l3_app_irq, +		IRQF_DISABLED | IRQF_TRIGGER_RISING, +		"l3-app-irq", l3); +	if (ret) { +		dev_err(&pdev->dev, "couldn't request app irq\n"); +		goto err2; +	} + +	return 0; + +err2: +	free_irq(l3->debug_irq, l3); +err1: +	iounmap(l3->rt); +err0: +	kfree(l3); +	return ret; +} + +static int __exit omap3_l3_remove(struct platform_device *pdev) +{ +	struct omap3_l3         *l3 = platform_get_drvdata(pdev); + +	free_irq(l3->app_irq, l3); +	free_irq(l3->debug_irq, l3); +	iounmap(l3->rt); +	kfree(l3); + +	return 0; +} + +static struct platform_driver omap3_l3_driver = { +	.remove         = __exit_p(omap3_l3_remove), +	.driver         = { +	.name   = "omap_l3_smx", +	}, +}; + +static int __init omap3_l3_init(void) +{ +	return platform_driver_probe(&omap3_l3_driver, omap3_l3_probe); +} +postcore_initcall_sync(omap3_l3_init); + +static void __exit omap3_l3_exit(void) +{ +	platform_driver_unregister(&omap3_l3_driver); +} +module_exit(omap3_l3_exit); diff --git a/drivers/bus/omap_l3_smx.h b/drivers/bus/omap_l3_smx.h new file mode 100644 index 00000000000..4f3cebca417 --- /dev/null +++ b/drivers/bus/omap_l3_smx.h @@ -0,0 +1,338 @@ +/* + * OMAP3XXX L3 Interconnect Driver header + * + * Copyright (C) 2011 Texas Corporation + *	Felipe Balbi <balbi@ti.com> + *	Santosh Shilimkar <santosh.shilimkar@ti.com> + *	sricharan <r.sricharan@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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +#ifndef __ARCH_ARM_MACH_OMAP2_L3_INTERCONNECT_3XXX_H +#define __ARCH_ARM_MACH_OMAP2_L3_INTERCONNECT_3XXX_H + +/* Register definitions. All 64-bit wide */ +#define L3_COMPONENT			0x000 +#define L3_CORE				0x018 +#define L3_AGENT_CONTROL		0x020 +#define L3_AGENT_STATUS			0x028 +#define L3_ERROR_LOG			0x058 + +#define L3_ERROR_LOG_MULTI		(1 << 31) +#define L3_ERROR_LOG_SECONDARY		(1 << 30) + +#define L3_ERROR_LOG_ADDR		0x060 + +/* Register definitions for Sideband Interconnect */ +#define L3_SI_CONTROL			0x020 +#define L3_SI_FLAG_STATUS_0		0x510 + +static const u64 shift = 1; + +#define L3_STATUS_0_MPUIA_BRST		(shift << 0) +#define L3_STATUS_0_MPUIA_RSP		(shift << 1) +#define L3_STATUS_0_MPUIA_INBAND	(shift << 2) +#define L3_STATUS_0_IVAIA_BRST		(shift << 6) +#define L3_STATUS_0_IVAIA_RSP		(shift << 7) +#define L3_STATUS_0_IVAIA_INBAND	(shift << 8) +#define L3_STATUS_0_SGXIA_BRST		(shift << 9) +#define L3_STATUS_0_SGXIA_RSP		(shift << 10) +#define L3_STATUS_0_SGXIA_MERROR	(shift << 11) +#define L3_STATUS_0_CAMIA_BRST		(shift << 12) +#define L3_STATUS_0_CAMIA_RSP		(shift << 13) +#define L3_STATUS_0_CAMIA_INBAND	(shift << 14) +#define L3_STATUS_0_DISPIA_BRST		(shift << 15) +#define L3_STATUS_0_DISPIA_RSP		(shift << 16) +#define L3_STATUS_0_DMARDIA_BRST	(shift << 18) +#define L3_STATUS_0_DMARDIA_RSP		(shift << 19) +#define L3_STATUS_0_DMAWRIA_BRST	(shift << 21) +#define L3_STATUS_0_DMAWRIA_RSP		(shift << 22) +#define L3_STATUS_0_USBOTGIA_BRST	(shift << 24) +#define L3_STATUS_0_USBOTGIA_RSP	(shift << 25) +#define L3_STATUS_0_USBOTGIA_INBAND	(shift << 26) +#define L3_STATUS_0_USBHOSTIA_BRST	(shift << 27) +#define L3_STATUS_0_USBHOSTIA_INBAND	(shift << 28) +#define L3_STATUS_0_SMSTA_REQ		(shift << 48) +#define L3_STATUS_0_GPMCTA_REQ		(shift << 49) +#define L3_STATUS_0_OCMRAMTA_REQ	(shift << 50) +#define L3_STATUS_0_OCMROMTA_REQ	(shift << 51) +#define L3_STATUS_0_IVATA_REQ		(shift << 54) +#define L3_STATUS_0_SGXTA_REQ		(shift << 55) +#define L3_STATUS_0_SGXTA_SERROR	(shift << 56) +#define L3_STATUS_0_GPMCTA_SERROR	(shift << 57) +#define L3_STATUS_0_L4CORETA_REQ	(shift << 58) +#define L3_STATUS_0_L4PERTA_REQ		(shift << 59) +#define L3_STATUS_0_L4EMUTA_REQ		(shift << 60) +#define L3_STATUS_0_MAD2DTA_REQ		(shift << 61) + +#define L3_STATUS_0_TIMEOUT_MASK	(L3_STATUS_0_MPUIA_BRST		\ +					| L3_STATUS_0_MPUIA_RSP		\ +					| L3_STATUS_0_IVAIA_BRST	\ +					| L3_STATUS_0_IVAIA_RSP		\ +					| L3_STATUS_0_SGXIA_BRST	\ +					| L3_STATUS_0_SGXIA_RSP		\ +					| L3_STATUS_0_CAMIA_BRST	\ +					| L3_STATUS_0_CAMIA_RSP		\ +					| L3_STATUS_0_DISPIA_BRST	\ +					| L3_STATUS_0_DISPIA_RSP	\ +					| L3_STATUS_0_DMARDIA_BRST	\ +					| L3_STATUS_0_DMARDIA_RSP	\ +					| L3_STATUS_0_DMAWRIA_BRST	\ +					| L3_STATUS_0_DMAWRIA_RSP	\ +					| L3_STATUS_0_USBOTGIA_BRST	\ +					| L3_STATUS_0_USBOTGIA_RSP	\ +					| L3_STATUS_0_USBHOSTIA_BRST	\ +					| L3_STATUS_0_SMSTA_REQ		\ +					| L3_STATUS_0_GPMCTA_REQ	\ +					| L3_STATUS_0_OCMRAMTA_REQ	\ +					| L3_STATUS_0_OCMROMTA_REQ	\ +					| L3_STATUS_0_IVATA_REQ		\ +					| L3_STATUS_0_SGXTA_REQ		\ +					| L3_STATUS_0_L4CORETA_REQ	\ +					| L3_STATUS_0_L4PERTA_REQ	\ +					| L3_STATUS_0_L4EMUTA_REQ	\ +					| L3_STATUS_0_MAD2DTA_REQ) + +#define L3_SI_FLAG_STATUS_1		0x530 + +#define L3_STATUS_1_MPU_DATAIA		(1 << 0) +#define L3_STATUS_1_DAPIA0		(1 << 3) +#define L3_STATUS_1_DAPIA1		(1 << 4) +#define L3_STATUS_1_IVAIA		(1 << 6) + +#define L3_PM_ERROR_LOG			0x020 +#define L3_PM_CONTROL			0x028 +#define L3_PM_ERROR_CLEAR_SINGLE	0x030 +#define L3_PM_ERROR_CLEAR_MULTI		0x038 +#define L3_PM_REQ_INFO_PERMISSION(n)	(0x048 + (0x020 * n)) +#define L3_PM_READ_PERMISSION(n)	(0x050 + (0x020 * n)) +#define L3_PM_WRITE_PERMISSION(n)	(0x058 + (0x020 * n)) +#define L3_PM_ADDR_MATCH(n)		(0x060 + (0x020 * n)) + +/* L3 error log bit fields. Common for IA and TA */ +#define L3_ERROR_LOG_CODE		24 +#define L3_ERROR_LOG_INITID		8 +#define L3_ERROR_LOG_CMD		0 + +/* L3 agent status bit fields. */ +#define L3_AGENT_STATUS_CLEAR_IA	0x10000000 +#define L3_AGENT_STATUS_CLEAR_TA	0x01000000 + +#define OMAP34xx_IRQ_L3_APP		10 +#define L3_APPLICATION_ERROR		0x0 +#define L3_DEBUG_ERROR			0x1 + +enum omap3_l3_initiator_id { +	/* LCD has 1 ID */ +	OMAP_L3_LCD = 29, +	/* SAD2D has 1 ID */ +	OMAP_L3_SAD2D = 28, +	/* MPU has 5 IDs */ +	OMAP_L3_IA_MPU_SS_1 = 27, +	OMAP_L3_IA_MPU_SS_2 = 26, +	OMAP_L3_IA_MPU_SS_3 = 25, +	OMAP_L3_IA_MPU_SS_4 = 24, +	OMAP_L3_IA_MPU_SS_5 = 23, +	/* IVA2.2 SS has 3 IDs*/ +	OMAP_L3_IA_IVA_SS_1 = 22, +	OMAP_L3_IA_IVA_SS_2 = 21, +	OMAP_L3_IA_IVA_SS_3 = 20, +	/* IVA 2.2 SS DMA has 6 IDS */ +	OMAP_L3_IA_IVA_SS_DMA_1 = 19, +	OMAP_L3_IA_IVA_SS_DMA_2 = 18, +	OMAP_L3_IA_IVA_SS_DMA_3 = 17, +	OMAP_L3_IA_IVA_SS_DMA_4 = 16, +	OMAP_L3_IA_IVA_SS_DMA_5 = 15, +	OMAP_L3_IA_IVA_SS_DMA_6 = 14, +	/* SGX has 1 ID */ +	OMAP_L3_IA_SGX = 13, +	/* CAM has 3 ID */ +	OMAP_L3_IA_CAM_1 = 12, +	OMAP_L3_IA_CAM_2 = 11, +	OMAP_L3_IA_CAM_3 = 10, +	/* DAP has 1 ID */ +	OMAP_L3_IA_DAP = 9, +	/* SDMA WR has 2 IDs */ +	OMAP_L3_SDMA_WR_1 = 8, +	OMAP_L3_SDMA_WR_2 = 7, +	/* SDMA RD has 4 IDs */ +	OMAP_L3_SDMA_RD_1 = 6, +	OMAP_L3_SDMA_RD_2 = 5, +	OMAP_L3_SDMA_RD_3 = 4, +	OMAP_L3_SDMA_RD_4 = 3, +	/* HSUSB OTG has 1 ID */ +	OMAP_L3_USBOTG = 2, +	/* HSUSB HOST has 1 ID */ +	OMAP_L3_USBHOST = 1, +}; + +enum omap3_l3_code { +	OMAP_L3_CODE_NOERROR = 0, +	OMAP_L3_CODE_UNSUP_CMD = 1, +	OMAP_L3_CODE_ADDR_HOLE = 2, +	OMAP_L3_CODE_PROTECT_VIOLATION = 3, +	OMAP_L3_CODE_IN_BAND_ERR = 4, +	/* codes 5 and 6 are reserved */ +	OMAP_L3_CODE_REQ_TOUT_NOT_ACCEPT = 7, +	OMAP_L3_CODE_REQ_TOUT_NO_RESP = 8, +	/* codes 9 - 15 are also reserved */ +}; + +struct omap3_l3 { +	struct device *dev; +	struct clk *ick; + +	/* memory base*/ +	void __iomem *rt; + +	int debug_irq; +	int app_irq; + +	/* true when and inband functional error occurs */ +	unsigned inband:1; +}; + +/* offsets for l3 agents in order with the Flag status register */ +static unsigned int omap3_l3_app_bases[] = { +	/* MPU IA */ +	0x1400, +	0x1400, +	0x1400, +	/* RESERVED */ +	0, +	0, +	0, +	/* IVA 2.2 IA */ +	0x1800, +	0x1800, +	0x1800, +	/* SGX IA */ +	0x1c00, +	0x1c00, +	/* RESERVED */ +	0, +	/* CAMERA IA */ +	0x5800, +	0x5800, +	0x5800, +	/* DISPLAY IA */ +	0x5400, +	0x5400, +	/* RESERVED */ +	0, +	/*SDMA RD IA */ +	0x4c00, +	0x4c00, +	/* RESERVED */ +	0, +	/* SDMA WR IA */ +	0x5000, +	0x5000, +	/* RESERVED */ +	0, +	/* USB OTG IA */ +	0x4400, +	0x4400, +	0x4400, +	/* USB HOST IA */ +	0x4000, +	0x4000, +	/* RESERVED */ +	0, +	0, +	0, +	0, +	/* SAD2D IA */ +	0x3000, +	0x3000, +	0x3000, +	/* RESERVED */ +	0, +	0, +	0, +	0, +	0, +	0, +	0, +	0, +	0, +	0, +	0, +	0, +	/* SMA TA */ +	0x2000, +	/* GPMC TA */ +	0x2400, +	/* OCM RAM TA */ +	0x2800, +	/* OCM ROM TA */ +	0x2C00, +	/* L4 CORE TA */ +	0x6800, +	/* L4 PER TA */ +	0x6c00, +	/* IVA 2.2 TA */ +	0x6000, +	/* SGX TA */ +	0x6400, +	/* L4 EMU TA */ +	0x7000, +	/* GPMC TA */ +	0x2400, +	/* L4 CORE TA */ +	0x6800, +	/* L4 PER TA */ +	0x6c00, +	/* L4 EMU TA */ +	0x7000, +	/* MAD2D TA */ +	0x3400, +	/* RESERVED */ +	0, +	0, +}; + +static unsigned int omap3_l3_debug_bases[] = { +	/* MPU DATA IA */ +	0x1400, +	/* RESERVED */ +	0, +	0, +	/* DAP IA */ +	0x5c00, +	0x5c00, +	/* RESERVED */ +	0, +	/* IVA 2.2 IA */ +	0x1800, +	/* REST RESERVED */ +}; + +static u32 *omap3_l3_bases[] = { +	omap3_l3_app_bases, +	omap3_l3_debug_bases, +}; + +/* + * REVISIT define __raw_readll/__raw_writell here, but move them to + * <asm/io.h> at some point + */ +#define __raw_writell(v, a)	(__chk_io_ptr(a), \ +				*(volatile u64 __force *)(a) = (v)) +#define __raw_readll(a)		(__chk_io_ptr(a), \ +				*(volatile u64 __force *)(a)) + +#endif diff --git a/drivers/char/nwflash.c b/drivers/char/nwflash.c index d45c3345b4a..a0e2f7d7035 100644 --- a/drivers/char/nwflash.c +++ b/drivers/char/nwflash.c @@ -30,7 +30,6 @@  #include <asm/hardware/dec21285.h>  #include <asm/io.h> -#include <asm/leds.h>  #include <asm/mach-types.h>  #include <asm/uaccess.h> @@ -179,9 +178,6 @@ static ssize_t flash_write(struct file *file, const char __user *buf,  	written = 0; -	leds_event(led_claim); -	leds_event(led_green_on); -  	nBlock = (int) p >> 16;	//block # of 64K bytes  	/* @@ -258,11 +254,6 @@ static ssize_t flash_write(struct file *file, const char __user *buf,  			printk(KERN_DEBUG "flash_write: written 0x%X bytes OK.\n", written);  	} -	/* -	 * restore reg on exit -	 */ -	leds_event(led_release); -  	mutex_unlock(&nwflash_mutex);  	return written; @@ -334,11 +325,6 @@ static int erase_block(int nBlock)  	int temp, temp1;  	/* -	 * orange LED == erase -	 */ -	leds_event(led_amber_on); - -	/*  	 * reset footbridge to the correct offset 0 (...0..3)  	 */  	*CSR_ROMWRITEREG = 0; @@ -446,12 +432,6 @@ static int write_block(unsigned long p, const char __user *buf, int count)  	unsigned long timeout;  	unsigned long timeout1; -	/* -	 * red LED == write -	 */ -	leds_event(led_amber_off); -	leds_event(led_red_on); -  	pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + p));  	/* @@ -558,17 +538,9 @@ static int write_block(unsigned long p, const char __user *buf, int count)  					       pWritePtr - FLASH_BASE);  				/* -				 * no LED == waiting -				 */ -				leds_event(led_amber_off); -				/*  				 * wait couple ms  				 */  				msleep(10); -				/* -				 * red LED == write -				 */ -				leds_event(led_red_on);  				goto WriteRetry;  			} else { @@ -583,12 +555,6 @@ static int write_block(unsigned long p, const char __user *buf, int count)  		}  	} -	/* -	 * green LED == read/verify -	 */ -	leds_event(led_amber_off); -	leds_event(led_green_on); -  	msleep(10);  	pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + p)); diff --git a/drivers/gpio/gpio-samsung.c b/drivers/gpio/gpio-samsung.c index 8af4b06e80f..a006f0db15a 100644 --- a/drivers/gpio/gpio-samsung.c +++ b/drivers/gpio/gpio-samsung.c @@ -2797,6 +2797,27 @@ static __init void exynos4_gpiolib_init(void)  	int group = 0;  	void __iomem *gpx_base; +#ifdef CONFIG_PINCTRL_SAMSUNG +		/* +		 * This gpio driver includes support for device tree support and +		 * there are platforms using it. In order to maintain +		 * compatibility with those platforms, and to allow non-dt +		 * Exynos4210 platforms to use this gpiolib support, a check +		 * is added to find out if there is a active pin-controller +		 * driver support available. If it is available, this gpiolib +		 * support is ignored and the gpiolib support available in +		 * pin-controller driver is used. This is a temporary check and +		 * will go away when all of the Exynos4210 platforms have +		 * switched to using device tree and the pin-ctrl driver. +		 */ +		struct device_node *pctrl_np; +		const char *pctrl_compat = "samsung,pinctrl-exynos4210"; +		pctrl_np = of_find_compatible_node(NULL, NULL, pctrl_compat); +		if (pctrl_np) +			if (of_device_is_available(pctrl_np)) +				return; +#endif +  	/* gpio part1 */  	gpio_base1 = ioremap(EXYNOS4_PA_GPIO1, SZ_4K);  	if (gpio_base1 == NULL) { diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 9a08c57bc93..f981ac4e678 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -27,6 +27,7 @@  #include <linux/slab.h>  #include <linux/i2c-tegra.h>  #include <linux/of_i2c.h> +#include <linux/of_device.h>  #include <linux/module.h>  #include <asm/unaligned.h> @@ -114,11 +115,21 @@ enum msg_end_type {  };  /** + * struct tegra_i2c_hw_feature : Different HW support on Tegra + * @has_continue_xfer_support: Continue transfer supports. + */ + +struct tegra_i2c_hw_feature { +	bool has_continue_xfer_support; +}; + +/**   * 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 - * @clk: clock reference for i2c controller - * @i2c_clk: clock reference for i2c bus + * @div_clk: clock reference for div clock of i2c controller. + * @fast_clk: clock reference for fast clock of i2c controller.   * @base: ioremapped registers cookie   * @cont_id: i2c controller id, used for for packet header   * @irq: irq number of transfer complete interrupt @@ -133,9 +144,10 @@ enum msg_end_type {   */  struct tegra_i2c_dev {  	struct device *dev; +	const struct tegra_i2c_hw_feature *hw;  	struct i2c_adapter adapter; -	struct clk *clk; -	struct clk *i2c_clk; +	struct clk *div_clk; +	struct clk *fast_clk;  	void __iomem *base;  	int cont_id;  	int irq; @@ -351,16 +363,40 @@ static void tegra_dvc_init(struct tegra_i2c_dev *i2c_dev)  	dvc_writel(i2c_dev, val, DVC_CTRL_REG1);  } +static inline int tegra_i2c_clock_enable(struct tegra_i2c_dev *i2c_dev) +{ +	int ret; +	ret = clk_prepare_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_prepare_enable(i2c_dev->div_clk); +	if (ret < 0) { +		dev_err(i2c_dev->dev, +			"Enabling div clk failed, err %d\n", ret); +		clk_disable_unprepare(i2c_dev->fast_clk); +	} +	return ret; +} + +static inline void tegra_i2c_clock_disable(struct tegra_i2c_dev *i2c_dev) +{ +	clk_disable_unprepare(i2c_dev->div_clk); +	clk_disable_unprepare(i2c_dev->fast_clk); +} +  static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)  {  	u32 val;  	int err = 0; -	clk_prepare_enable(i2c_dev->clk); +	tegra_i2c_clock_enable(i2c_dev); -	tegra_periph_reset_assert(i2c_dev->clk); +	tegra_periph_reset_assert(i2c_dev->div_clk);  	udelay(2); -	tegra_periph_reset_deassert(i2c_dev->clk); +	tegra_periph_reset_deassert(i2c_dev->div_clk);  	if (i2c_dev->is_dvc)  		tegra_dvc_init(i2c_dev); @@ -369,7 +405,7 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)  		(0x2 << I2C_CNFG_DEBOUNCE_CNT_SHIFT);  	i2c_writel(i2c_dev, val, I2C_CNFG);  	i2c_writel(i2c_dev, 0, I2C_INT_MASK); -	clk_set_rate(i2c_dev->clk, i2c_dev->bus_clk_rate * 8); +	clk_set_rate(i2c_dev->div_clk, i2c_dev->bus_clk_rate * 8);  	if (!i2c_dev->is_dvc) {  		u32 sl_cfg = i2c_readl(i2c_dev, I2C_SL_CNFG); @@ -387,7 +423,7 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)  	if (tegra_i2c_flush_fifos(i2c_dev))  		err = -ETIMEDOUT; -	clk_disable_unprepare(i2c_dev->clk); +	tegra_i2c_clock_disable(i2c_dev);  	if (i2c_dev->irq_disabled) {  		i2c_dev->irq_disabled = 0; @@ -563,7 +599,7 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],  	if (i2c_dev->is_suspended)  		return -EBUSY; -	clk_prepare_enable(i2c_dev->clk); +	tegra_i2c_clock_enable(i2c_dev);  	for (i = 0; i < num; i++) {  		enum msg_end_type end_type = MSG_END_STOP;  		if (i < (num - 1)) { @@ -576,14 +612,19 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],  		if (ret)  			break;  	} -	clk_disable_unprepare(i2c_dev->clk); +	tegra_i2c_clock_disable(i2c_dev);  	return ret ?: i;  }  static u32 tegra_i2c_func(struct i2c_adapter *adap)  { -	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT_ADDR | -		I2C_FUNC_PROTOCOL_MANGLING | I2C_FUNC_NOSTART; +	struct tegra_i2c_dev *i2c_dev = i2c_get_adapdata(adap); +	u32 ret = I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT_ADDR | +				I2C_FUNC_PROTOCOL_MANGLING; + +	if (i2c_dev->hw->has_continue_xfer_support) +		ret |= I2C_FUNC_NOSTART; +	return ret;  }  static const struct i2c_algorithm tegra_i2c_algo = { @@ -591,13 +632,32 @@ static const struct i2c_algorithm tegra_i2c_algo = {  	.functionality	= tegra_i2c_func,  }; +static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { +	.has_continue_xfer_support = false, +}; + +static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { +	.has_continue_xfer_support = true, +}; + +#if defined(CONFIG_OF) +/* Match table for of_platform binding */ +static const struct of_device_id tegra_i2c_of_match[] __devinitconst = { +	{ .compatible = "nvidia,tegra30-i2c", .data = &tegra30_i2c_hw, }, +	{ .compatible = "nvidia,tegra20-i2c", .data = &tegra20_i2c_hw, }, +	{ .compatible = "nvidia,tegra20-i2c-dvc", .data = &tegra20_i2c_hw, }, +	{}, +}; +MODULE_DEVICE_TABLE(of, tegra_i2c_of_match); +#endif +  static int __devinit tegra_i2c_probe(struct platform_device *pdev)  {  	struct tegra_i2c_dev *i2c_dev;  	struct tegra_i2c_platform_data *pdata = pdev->dev.platform_data;  	struct resource *res; -	struct clk *clk; -	struct clk *i2c_clk; +	struct clk *div_clk; +	struct clk *fast_clk;  	const unsigned int *prop;  	void __iomem *base;  	int irq; @@ -622,16 +682,16 @@ static int __devinit tegra_i2c_probe(struct platform_device *pdev)  	}  	irq = res->start; -	clk = devm_clk_get(&pdev->dev, NULL); -	if (IS_ERR(clk)) { +	div_clk = devm_clk_get(&pdev->dev, "div-clk"); +	if (IS_ERR(div_clk)) {  		dev_err(&pdev->dev, "missing controller clock"); -		return PTR_ERR(clk); +		return PTR_ERR(div_clk);  	} -	i2c_clk = devm_clk_get(&pdev->dev, "i2c"); -	if (IS_ERR(i2c_clk)) { +	fast_clk = devm_clk_get(&pdev->dev, "fast-clk"); +	if (IS_ERR(fast_clk)) {  		dev_err(&pdev->dev, "missing bus clock"); -		return PTR_ERR(i2c_clk); +		return PTR_ERR(fast_clk);  	}  	i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL); @@ -641,8 +701,8 @@ static int __devinit tegra_i2c_probe(struct platform_device *pdev)  	}  	i2c_dev->base = base; -	i2c_dev->clk = clk; -	i2c_dev->i2c_clk = i2c_clk; +	i2c_dev->div_clk = div_clk; +	i2c_dev->fast_clk = fast_clk;  	i2c_dev->adapter.algo = &tegra_i2c_algo;  	i2c_dev->irq = irq;  	i2c_dev->cont_id = pdev->id; @@ -659,11 +719,18 @@ static int __devinit tegra_i2c_probe(struct platform_device *pdev)  			i2c_dev->bus_clk_rate = be32_to_cpup(prop);  	} -	if (pdev->dev.of_node) +	i2c_dev->hw = &tegra20_i2c_hw; + +	if (pdev->dev.of_node) { +		const struct of_device_id *match; +		match = of_match_device(of_match_ptr(tegra_i2c_of_match), +						&pdev->dev); +		i2c_dev->hw = match->data;  		i2c_dev->is_dvc = of_device_is_compatible(pdev->dev.of_node,  						"nvidia,tegra20-i2c-dvc"); -	else if (pdev->id == 3) +	} else if (pdev->id == 3) {  		i2c_dev->is_dvc = 1; +	}  	init_completion(&i2c_dev->msg_complete);  	platform_set_drvdata(pdev, i2c_dev); @@ -681,8 +748,6 @@ static int __devinit tegra_i2c_probe(struct platform_device *pdev)  		return ret;  	} -	clk_prepare_enable(i2c_dev->i2c_clk); -  	i2c_set_adapdata(&i2c_dev->adapter, i2c_dev);  	i2c_dev->adapter.owner = THIS_MODULE;  	i2c_dev->adapter.class = I2C_CLASS_HWMON; @@ -696,7 +761,6 @@ static int __devinit tegra_i2c_probe(struct platform_device *pdev)  	ret = i2c_add_numbered_adapter(&i2c_dev->adapter);  	if (ret) {  		dev_err(&pdev->dev, "Failed to add I2C adapter\n"); -		clk_disable_unprepare(i2c_dev->i2c_clk);  		return ret;  	} @@ -751,16 +815,6 @@ static SIMPLE_DEV_PM_OPS(tegra_i2c_pm, tegra_i2c_suspend, tegra_i2c_resume);  #define TEGRA_I2C_PM	NULL  #endif -#if defined(CONFIG_OF) -/* Match table for of_platform binding */ -static const struct of_device_id tegra_i2c_of_match[] __devinitconst = { -	{ .compatible = "nvidia,tegra20-i2c", }, -	{ .compatible = "nvidia,tegra20-i2c-dvc", }, -	{}, -}; -MODULE_DEVICE_TABLE(of, tegra_i2c_of_match); -#endif -  static struct platform_driver tegra_i2c_driver = {  	.probe   = tegra_i2c_probe,  	.remove  = __devexit_p(tegra_i2c_remove), diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index c96bbaadeeb..16578d3b52b 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -506,6 +506,16 @@ config LEDS_TRIGGER_BACKLIGHT  	  If unsure, say N. +config LEDS_TRIGGER_CPU +	bool "LED CPU Trigger" +	depends on LEDS_TRIGGERS +	help +	  This allows LEDs to be controlled by active CPUs. This shows +	  the active CPUs across an array of LEDs so you can see which +	  CPUs are active on the system at any given moment. + +	  If unsure, say N. +  config LEDS_TRIGGER_GPIO  	tristate "LED GPIO Trigger"  	depends on LEDS_TRIGGERS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index a4429a9217b..a9b627c4f8b 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -61,5 +61,6 @@ obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK)	+= ledtrig-ide-disk.o  obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT)	+= ledtrig-heartbeat.o  obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT)	+= ledtrig-backlight.o  obj-$(CONFIG_LEDS_TRIGGER_GPIO)		+= ledtrig-gpio.o +obj-$(CONFIG_LEDS_TRIGGER_CPU)		+= ledtrig-cpu.o  obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON)	+= ledtrig-default-on.o  obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT)	+= ledtrig-transient.o diff --git a/drivers/leds/ledtrig-cpu.c b/drivers/leds/ledtrig-cpu.c new file mode 100644 index 00000000000..b312056da14 --- /dev/null +++ b/drivers/leds/ledtrig-cpu.c @@ -0,0 +1,163 @@ +/* + * ledtrig-cpu.c - LED trigger based on CPU activity + * + * This LED trigger will be registered for each possible CPU and named as + * cpu0, cpu1, cpu2, cpu3, etc. + * + * It can be bound to any LED just like other triggers using either a + * board file or via sysfs interface. + * + * An API named ledtrig_cpu is exported for any user, who want to add CPU + * activity indication in their code + * + * Copyright 2011 Linus Walleij <linus.walleij@linaro.org> + * Copyright 2011 - 2012 Bryan Wu <bryan.wu@canonical.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/percpu.h> +#include <linux/syscore_ops.h> +#include <linux/rwsem.h> +#include "leds.h" + +#define MAX_NAME_LEN	8 + +struct led_trigger_cpu { +	char name[MAX_NAME_LEN]; +	struct led_trigger *_trig; +	struct mutex lock; +	int lock_is_inited; +}; + +static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); + +/** + * ledtrig_cpu - emit a CPU event as a trigger + * @evt: CPU event to be emitted + * + * Emit a CPU event on a CPU core, which will trigger a + * binded LED to turn on or turn off. + */ +void ledtrig_cpu(enum cpu_led_event ledevt) +{ +	struct led_trigger_cpu *trig = &__get_cpu_var(cpu_trig); + +	/* mutex lock should be initialized before calling mutex_call() */ +	if (!trig->lock_is_inited) +		return; + +	mutex_lock(&trig->lock); + +	/* Locate the correct CPU LED */ +	switch (ledevt) { +	case CPU_LED_IDLE_END: +	case CPU_LED_START: +		/* Will turn the LED on, max brightness */ +		led_trigger_event(trig->_trig, LED_FULL); +		break; + +	case CPU_LED_IDLE_START: +	case CPU_LED_STOP: +	case CPU_LED_HALTED: +		/* Will turn the LED off */ +		led_trigger_event(trig->_trig, LED_OFF); +		break; + +	default: +		/* Will leave the LED as it is */ +		break; +	} + +	mutex_unlock(&trig->lock); +} +EXPORT_SYMBOL(ledtrig_cpu); + +static int ledtrig_cpu_syscore_suspend(void) +{ +	ledtrig_cpu(CPU_LED_STOP); +	return 0; +} + +static void ledtrig_cpu_syscore_resume(void) +{ +	ledtrig_cpu(CPU_LED_START); +} + +static void ledtrig_cpu_syscore_shutdown(void) +{ +	ledtrig_cpu(CPU_LED_HALTED); +} + +static struct syscore_ops ledtrig_cpu_syscore_ops = { +	.shutdown	= ledtrig_cpu_syscore_shutdown, +	.suspend	= ledtrig_cpu_syscore_suspend, +	.resume		= ledtrig_cpu_syscore_resume, +}; + +static int __init ledtrig_cpu_init(void) +{ +	int cpu; + +	/* Supports up to 9999 cpu cores */ +	BUILD_BUG_ON(CONFIG_NR_CPUS > 9999); + +	/* +	 * Registering CPU led trigger for each CPU core here +	 * ignores CPU hotplug, but after this CPU hotplug works +	 * fine with this trigger. +	 */ +	for_each_possible_cpu(cpu) { +		struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); + +		mutex_init(&trig->lock); + +		snprintf(trig->name, MAX_NAME_LEN, "cpu%d", cpu); + +		mutex_lock(&trig->lock); +		led_trigger_register_simple(trig->name, &trig->_trig); +		trig->lock_is_inited = 1; +		mutex_unlock(&trig->lock); +	} + +	register_syscore_ops(&ledtrig_cpu_syscore_ops); + +	pr_info("ledtrig-cpu: registered to indicate activity on CPUs\n"); + +	return 0; +} +module_init(ledtrig_cpu_init); + +static void __exit ledtrig_cpu_exit(void) +{ +	int cpu; + +	for_each_possible_cpu(cpu) { +		struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); + +		mutex_lock(&trig->lock); + +		led_trigger_unregister_simple(trig->_trig); +		trig->_trig = NULL; +		memset(trig->name, 0, MAX_NAME_LEN); +		trig->lock_is_inited = 0; + +		mutex_unlock(&trig->lock); +		mutex_destroy(&trig->lock); +	} + +	unregister_syscore_ops(&ledtrig_cpu_syscore_ops); +} +module_exit(ledtrig_cpu_exit); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_AUTHOR("Bryan Wu <bryan.wu@canonical.com>"); +MODULE_DESCRIPTION("CPU LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 8ca417614c5..598cd0a3ade 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -406,46 +406,6 @@ config MTD_NAND_ATMEL  	help  	  Enables support for NAND Flash / Smart Media Card interface  	  on Atmel AT91 and AVR32 processors. -choice -	prompt "ECC management for NAND Flash / SmartMedia on AT91 / AVR32" -	depends on MTD_NAND_ATMEL - -config MTD_NAND_ATMEL_ECC_HW -	bool "Hardware ECC" -	depends on ARCH_AT91SAM9263 || ARCH_AT91SAM9260 || AVR32 -	help -	  Use hardware ECC instead of software ECC when the chip -	  supports it. - -	  The hardware ECC controller is capable of single bit error -	  correction and 2-bit random detection per page. - -	  NB : hardware and software ECC schemes are incompatible. -	  If you switch from one to another, you'll have to erase your -	  mtd partition. - -	  If unsure, say Y - -config MTD_NAND_ATMEL_ECC_SOFT -	bool "Software ECC" -	help -	  Use software ECC. - -	  NB : hardware and software ECC schemes are incompatible. -	  If you switch from one to another, you'll have to erase your -	  mtd partition. - -config MTD_NAND_ATMEL_ECC_NONE -	bool "No ECC (testing only, DANGEROUS)" -	depends on DEBUG_KERNEL -	help -	  No ECC will be used. -	  It's not a good idea and it should be reserved for testing -	  purpose only. - -	  If unsure, say N - -endchoice  config MTD_NAND_PXA3xx  	tristate "Support for NAND flash devices on PXA3xx" diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 54e3588bef6..34e94c7f68c 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -145,6 +145,15 @@ config PINCTRL_COH901  	  COH 901 335 and COH 901 571/3. They contain 3, 5 or 7  	  ports of 8 GPIO pins each. +config PINCTRL_SAMSUNG +	bool "Samsung pinctrl driver" +	select PINMUX +	select PINCONF + +config PINCTRL_EXYNOS4 +	bool "Pinctrl driver data for Exynos4 SoC" +	select PINCTRL_SAMSUNG +  source "drivers/pinctrl/spear/Kconfig"  endmenu diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index f40b1f81ff2..6a88113e11d 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -29,5 +29,7 @@ obj-$(CONFIG_PINCTRL_TEGRA20)	+= pinctrl-tegra20.o  obj-$(CONFIG_PINCTRL_TEGRA30)	+= pinctrl-tegra30.o  obj-$(CONFIG_PINCTRL_U300)	+= pinctrl-u300.o  obj-$(CONFIG_PINCTRL_COH901)	+= pinctrl-coh901.o +obj-$(CONFIG_PINCTRL_SAMSUNG)	+= pinctrl-samsung.o +obj-$(CONFIG_PINCTRL_EXYNOS4)	+= pinctrl-exynos.o  obj-$(CONFIG_PLAT_SPEAR)	+= spear/ diff --git a/drivers/pinctrl/pinctrl-exynos.c b/drivers/pinctrl/pinctrl-exynos.c new file mode 100644 index 00000000000..21362f48d37 --- /dev/null +++ b/drivers/pinctrl/pinctrl-exynos.c @@ -0,0 +1,579 @@ +/* + * Exynos specific support for Samsung pinctrl/gpiolib driver with eint support. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + *		http://www.samsung.com + * Copyright (c) 2012 Linaro Ltd + *		http://www.linaro.org + * + * Author: Thomas Abraham <thomas.ab@samsung.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; either version 2 of the License, or + * (at your option) any later version. + * + * This file contains the Samsung Exynos specific information required by the + * the Samsung pinctrl/gpiolib driver. It also includes the implementation of + * external gpio and wakeup interrupt support. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/irq.h> +#include <linux/of_irq.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/err.h> + +#include <asm/mach/irq.h> + +#include "pinctrl-samsung.h" +#include "pinctrl-exynos.h" + +/* list of external wakeup controllers supported */ +static const struct of_device_id exynos_wkup_irq_ids[] = { +	{ .compatible = "samsung,exynos4210-wakeup-eint", }, +}; + +static void exynos_gpio_irq_unmask(struct irq_data *irqd) +{ +	struct samsung_pinctrl_drv_data *d = irqd->domain->host_data; +	struct exynos_geint_data *edata = irq_data_get_irq_handler_data(irqd); +	unsigned long reg_mask = d->ctrl->geint_mask + edata->eint_offset; +	unsigned long mask; + +	mask = readl(d->virt_base + reg_mask); +	mask &= ~(1 << edata->pin); +	writel(mask, d->virt_base + reg_mask); +} + +static void exynos_gpio_irq_mask(struct irq_data *irqd) +{ +	struct samsung_pinctrl_drv_data *d = irqd->domain->host_data; +	struct exynos_geint_data *edata = irq_data_get_irq_handler_data(irqd); +	unsigned long reg_mask = d->ctrl->geint_mask + edata->eint_offset; +	unsigned long mask; + +	mask = readl(d->virt_base + reg_mask); +	mask |= 1 << edata->pin; +	writel(mask, d->virt_base + reg_mask); +} + +static void exynos_gpio_irq_ack(struct irq_data *irqd) +{ +	struct samsung_pinctrl_drv_data *d = irqd->domain->host_data; +	struct exynos_geint_data *edata = irq_data_get_irq_handler_data(irqd); +	unsigned long reg_pend = d->ctrl->geint_pend + edata->eint_offset; + +	writel(1 << edata->pin, d->virt_base + reg_pend); +} + +static int exynos_gpio_irq_set_type(struct irq_data *irqd, unsigned int type) +{ +	struct samsung_pinctrl_drv_data *d = irqd->domain->host_data; +	struct samsung_pin_ctrl *ctrl = d->ctrl; +	struct exynos_geint_data *edata = irq_data_get_irq_handler_data(irqd); +	struct samsung_pin_bank *bank = edata->bank; +	unsigned int shift = EXYNOS_EINT_CON_LEN * edata->pin; +	unsigned int con, trig_type; +	unsigned long reg_con = ctrl->geint_con + edata->eint_offset; +	unsigned int mask; + +	switch (type) { +	case IRQ_TYPE_EDGE_RISING: +		trig_type = EXYNOS_EINT_EDGE_RISING; +		break; +	case IRQ_TYPE_EDGE_FALLING: +		trig_type = EXYNOS_EINT_EDGE_FALLING; +		break; +	case IRQ_TYPE_EDGE_BOTH: +		trig_type = EXYNOS_EINT_EDGE_BOTH; +		break; +	case IRQ_TYPE_LEVEL_HIGH: +		trig_type = EXYNOS_EINT_LEVEL_HIGH; +		break; +	case IRQ_TYPE_LEVEL_LOW: +		trig_type = EXYNOS_EINT_LEVEL_LOW; +		break; +	default: +		pr_err("unsupported external interrupt type\n"); +		return -EINVAL; +	} + +	if (type & IRQ_TYPE_EDGE_BOTH) +		__irq_set_handler_locked(irqd->irq, handle_edge_irq); +	else +		__irq_set_handler_locked(irqd->irq, handle_level_irq); + +	con = readl(d->virt_base + reg_con); +	con &= ~(EXYNOS_EINT_CON_MASK << shift); +	con |= trig_type << shift; +	writel(con, d->virt_base + reg_con); + +	reg_con = bank->pctl_offset; +	shift = edata->pin * bank->func_width; +	mask = (1 << bank->func_width) - 1; + +	con = readl(d->virt_base + reg_con); +	con &= ~(mask << shift); +	con |= EXYNOS_EINT_FUNC << shift; +	writel(con, d->virt_base + reg_con); + +	return 0; +} + +/* + * irq_chip for gpio interrupts. + */ +static struct irq_chip exynos_gpio_irq_chip = { +	.name		= "exynos_gpio_irq_chip", +	.irq_unmask	= exynos_gpio_irq_unmask, +	.irq_mask	= exynos_gpio_irq_mask, +	.irq_ack		= exynos_gpio_irq_ack, +	.irq_set_type	= exynos_gpio_irq_set_type, +}; + +/* + * given a controller-local external gpio interrupt number, prepare the handler + * data for it. + */ +static struct exynos_geint_data *exynos_get_eint_data(irq_hw_number_t hw, +				struct samsung_pinctrl_drv_data *d) +{ +	struct samsung_pin_bank *bank = d->ctrl->pin_banks; +	struct exynos_geint_data *eint_data; +	unsigned int nr_banks = d->ctrl->nr_banks, idx; +	unsigned int irq_base = 0, eint_offset = 0; + +	if (hw >= d->ctrl->nr_gint) { +		dev_err(d->dev, "unsupported ext-gpio interrupt\n"); +		return NULL; +	} + +	for (idx = 0; idx < nr_banks; idx++, bank++) { +		if (bank->eint_type != EINT_TYPE_GPIO) +			continue; +		if ((hw >= irq_base) && (hw < (irq_base + bank->nr_pins))) +			break; +		irq_base += bank->nr_pins; +		eint_offset += 4; +	} + +	if (idx == nr_banks) { +		dev_err(d->dev, "pin bank not found for ext-gpio interrupt\n"); +		return NULL; +	} + +	eint_data = devm_kzalloc(d->dev, sizeof(*eint_data), GFP_KERNEL); +	if (!eint_data) { +		dev_err(d->dev, "no memory for eint-gpio data\n"); +		return NULL; +	} + +	eint_data->bank	= bank; +	eint_data->pin = hw - irq_base; +	eint_data->eint_offset = eint_offset; +	return eint_data; +} + +static int exynos_gpio_irq_map(struct irq_domain *h, unsigned int virq, +					irq_hw_number_t hw) +{ +	struct samsung_pinctrl_drv_data *d = h->host_data; +	struct exynos_geint_data *eint_data; + +	eint_data = exynos_get_eint_data(hw, d); +	if (!eint_data) +		return -EINVAL; + +	irq_set_handler_data(virq, eint_data); +	irq_set_chip_data(virq, h->host_data); +	irq_set_chip_and_handler(virq, &exynos_gpio_irq_chip, +					handle_level_irq); +	set_irq_flags(virq, IRQF_VALID); +	return 0; +} + +static void exynos_gpio_irq_unmap(struct irq_domain *h, unsigned int virq) +{ +	struct samsung_pinctrl_drv_data *d = h->host_data; +	struct exynos_geint_data *eint_data; + +	eint_data = irq_get_handler_data(virq); +	devm_kfree(d->dev, eint_data); +} + +/* + * irq domain callbacks for external gpio interrupt controller. + */ +static const struct irq_domain_ops exynos_gpio_irqd_ops = { +	.map	= exynos_gpio_irq_map, +	.unmap	= exynos_gpio_irq_unmap, +	.xlate	= irq_domain_xlate_twocell, +}; + +static irqreturn_t exynos_eint_gpio_irq(int irq, void *data) +{ +	struct samsung_pinctrl_drv_data *d = data; +	struct samsung_pin_ctrl *ctrl = d->ctrl; +	struct samsung_pin_bank *bank = ctrl->pin_banks; +	unsigned int svc, group, pin, virq; + +	svc = readl(d->virt_base + ctrl->svc); +	group = EXYNOS_SVC_GROUP(svc); +	pin = svc & EXYNOS_SVC_NUM_MASK; + +	if (!group) +		return IRQ_HANDLED; +	bank += (group - 1); + +	virq = irq_linear_revmap(d->gpio_irqd, bank->irq_base + pin); +	if (!virq) +		return IRQ_NONE; +	generic_handle_irq(virq); +	return IRQ_HANDLED; +} + +/* + * exynos_eint_gpio_init() - setup handling of external gpio interrupts. + * @d: driver data of samsung pinctrl driver. + */ +static int exynos_eint_gpio_init(struct samsung_pinctrl_drv_data *d) +{ +	struct device *dev = d->dev; +	unsigned int ret; + +	if (!d->irq) { +		dev_err(dev, "irq number not available\n"); +		return -EINVAL; +	} + +	ret = devm_request_irq(dev, d->irq, exynos_eint_gpio_irq, +					0, dev_name(dev), d); +	if (ret) { +		dev_err(dev, "irq request failed\n"); +		return -ENXIO; +	} + +	d->gpio_irqd = irq_domain_add_linear(dev->of_node, d->ctrl->nr_gint, +				&exynos_gpio_irqd_ops, d); +	if (!d->gpio_irqd) { +		dev_err(dev, "gpio irq domain allocation failed\n"); +		return -ENXIO; +	} + +	return 0; +} + +static void exynos_wkup_irq_unmask(struct irq_data *irqd) +{ +	struct samsung_pinctrl_drv_data *d = irq_data_get_irq_chip_data(irqd); +	unsigned int bank = irqd->hwirq / EXYNOS_EINT_MAX_PER_BANK; +	unsigned int pin = irqd->hwirq & (EXYNOS_EINT_MAX_PER_BANK - 1); +	unsigned long reg_mask = d->ctrl->weint_mask + (bank << 2); +	unsigned long mask; + +	mask = readl(d->virt_base + reg_mask); +	mask &= ~(1 << pin); +	writel(mask, d->virt_base + reg_mask); +} + +static void exynos_wkup_irq_mask(struct irq_data *irqd) +{ +	struct samsung_pinctrl_drv_data *d = irq_data_get_irq_chip_data(irqd); +	unsigned int bank = irqd->hwirq / EXYNOS_EINT_MAX_PER_BANK; +	unsigned int pin = irqd->hwirq & (EXYNOS_EINT_MAX_PER_BANK - 1); +	unsigned long reg_mask = d->ctrl->weint_mask + (bank << 2); +	unsigned long mask; + +	mask = readl(d->virt_base + reg_mask); +	mask |= 1 << pin; +	writel(mask, d->virt_base + reg_mask); +} + +static void exynos_wkup_irq_ack(struct irq_data *irqd) +{ +	struct samsung_pinctrl_drv_data *d = irq_data_get_irq_chip_data(irqd); +	unsigned int bank = irqd->hwirq / EXYNOS_EINT_MAX_PER_BANK; +	unsigned int pin = irqd->hwirq & (EXYNOS_EINT_MAX_PER_BANK - 1); +	unsigned long pend = d->ctrl->weint_pend + (bank << 2); + +	writel(1 << pin, d->virt_base + pend); +} + +static int exynos_wkup_irq_set_type(struct irq_data *irqd, unsigned int type) +{ +	struct samsung_pinctrl_drv_data *d = irq_data_get_irq_chip_data(irqd); +	unsigned int bank = irqd->hwirq / EXYNOS_EINT_MAX_PER_BANK; +	unsigned int pin = irqd->hwirq & (EXYNOS_EINT_MAX_PER_BANK - 1); +	unsigned long reg_con = d->ctrl->weint_con + (bank << 2); +	unsigned long shift = EXYNOS_EINT_CON_LEN * pin; +	unsigned long con, trig_type; + +	switch (type) { +	case IRQ_TYPE_EDGE_RISING: +		trig_type = EXYNOS_EINT_EDGE_RISING; +		break; +	case IRQ_TYPE_EDGE_FALLING: +		trig_type = EXYNOS_EINT_EDGE_FALLING; +		break; +	case IRQ_TYPE_EDGE_BOTH: +		trig_type = EXYNOS_EINT_EDGE_BOTH; +		break; +	case IRQ_TYPE_LEVEL_HIGH: +		trig_type = EXYNOS_EINT_LEVEL_HIGH; +		break; +	case IRQ_TYPE_LEVEL_LOW: +		trig_type = EXYNOS_EINT_LEVEL_LOW; +		break; +	default: +		pr_err("unsupported external interrupt type\n"); +		return -EINVAL; +	} + +	if (type & IRQ_TYPE_EDGE_BOTH) +		__irq_set_handler_locked(irqd->irq, handle_edge_irq); +	else +		__irq_set_handler_locked(irqd->irq, handle_level_irq); + +	con = readl(d->virt_base + reg_con); +	con &= ~(EXYNOS_EINT_CON_MASK << shift); +	con |= trig_type << shift; +	writel(con, d->virt_base + reg_con); +	return 0; +} + +/* + * irq_chip for wakeup interrupts + */ +static struct irq_chip exynos_wkup_irq_chip = { +	.name	= "exynos_wkup_irq_chip", +	.irq_unmask	= exynos_wkup_irq_unmask, +	.irq_mask	= exynos_wkup_irq_mask, +	.irq_ack	= exynos_wkup_irq_ack, +	.irq_set_type	= exynos_wkup_irq_set_type, +}; + +/* interrupt handler for wakeup interrupts 0..15 */ +static void exynos_irq_eint0_15(unsigned int irq, struct irq_desc *desc) +{ +	struct exynos_weint_data *eintd = irq_get_handler_data(irq); +	struct irq_chip *chip = irq_get_chip(irq); +	int eint_irq; + +	chained_irq_enter(chip, desc); +	chip->irq_mask(&desc->irq_data); + +	if (chip->irq_ack) +		chip->irq_ack(&desc->irq_data); + +	eint_irq = irq_linear_revmap(eintd->domain, eintd->irq); +	generic_handle_irq(eint_irq); +	chip->irq_unmask(&desc->irq_data); +	chained_irq_exit(chip, desc); +} + +static inline void exynos_irq_demux_eint(int irq_base, unsigned long pend, +					struct irq_domain *domain) +{ +	unsigned int irq; + +	while (pend) { +		irq = fls(pend) - 1; +		generic_handle_irq(irq_find_mapping(domain, irq_base + irq)); +		pend &= ~(1 << irq); +	} +} + +/* interrupt handler for wakeup interrupt 16 */ +static void exynos_irq_demux_eint16_31(unsigned int irq, struct irq_desc *desc) +{ +	struct irq_chip *chip = irq_get_chip(irq); +	struct exynos_weint_data *eintd = irq_get_handler_data(irq); +	struct samsung_pinctrl_drv_data *d = eintd->domain->host_data; +	unsigned long pend; +	unsigned long mask; + +	chained_irq_enter(chip, desc); +	pend = readl(d->virt_base + d->ctrl->weint_pend + 0x8); +	mask = readl(d->virt_base + d->ctrl->weint_mask + 0x8); +	exynos_irq_demux_eint(16, pend & ~mask, eintd->domain); +	pend = readl(d->virt_base + d->ctrl->weint_pend + 0xC); +	mask = readl(d->virt_base + d->ctrl->weint_mask + 0xC); +	exynos_irq_demux_eint(24, pend & ~mask, eintd->domain); +	chained_irq_exit(chip, desc); +} + +static int exynos_wkup_irq_map(struct irq_domain *h, unsigned int virq, +					irq_hw_number_t hw) +{ +	irq_set_chip_and_handler(virq, &exynos_wkup_irq_chip, handle_level_irq); +	irq_set_chip_data(virq, h->host_data); +	set_irq_flags(virq, IRQF_VALID); +	return 0; +} + +/* + * irq domain callbacks for external wakeup interrupt controller. + */ +static const struct irq_domain_ops exynos_wkup_irqd_ops = { +	.map	= exynos_wkup_irq_map, +	.xlate	= irq_domain_xlate_twocell, +}; + +/* + * exynos_eint_wkup_init() - setup handling of external wakeup interrupts. + * @d: driver data of samsung pinctrl driver. + */ +static int exynos_eint_wkup_init(struct samsung_pinctrl_drv_data *d) +{ +	struct device *dev = d->dev; +	struct device_node *wkup_np = NULL; +	struct device_node *np; +	struct exynos_weint_data *weint_data; +	int idx, irq; + +	for_each_child_of_node(dev->of_node, np) { +		if (of_match_node(exynos_wkup_irq_ids, np)) { +			wkup_np = np; +			break; +		} +	} +	if (!wkup_np) +		return -ENODEV; + +	d->wkup_irqd = irq_domain_add_linear(wkup_np, d->ctrl->nr_wint, +				&exynos_wkup_irqd_ops, d); +	if (!d->wkup_irqd) { +		dev_err(dev, "wakeup irq domain allocation failed\n"); +		return -ENXIO; +	} + +	weint_data = devm_kzalloc(dev, sizeof(*weint_data) * 17, GFP_KERNEL); +	if (!weint_data) { +		dev_err(dev, "could not allocate memory for weint_data\n"); +		return -ENOMEM; +	} + +	irq = irq_of_parse_and_map(wkup_np, 16); +	if (irq) { +		weint_data[16].domain = d->wkup_irqd; +		irq_set_chained_handler(irq, exynos_irq_demux_eint16_31); +		irq_set_handler_data(irq, &weint_data[16]); +	} else { +		dev_err(dev, "irq number for EINT16-32 not found\n"); +	} + +	for (idx = 0; idx < 16; idx++) { +		weint_data[idx].domain = d->wkup_irqd; +		weint_data[idx].irq = idx; + +		irq = irq_of_parse_and_map(wkup_np, idx); +		if (irq) { +			irq_set_handler_data(irq, &weint_data[idx]); +			irq_set_chained_handler(irq, exynos_irq_eint0_15); +		} else { +			dev_err(dev, "irq number for eint-%x not found\n", idx); +		} +	} +	return 0; +} + +/* pin banks of exynos4210 pin-controller 0 */ +static struct samsung_pin_bank exynos4210_pin_banks0[] = { +	EXYNOS_PIN_BANK_EINTG(0x000, EXYNOS4210_GPIO_A0, "gpa0"), +	EXYNOS_PIN_BANK_EINTG(0x020, EXYNOS4210_GPIO_A1, "gpa1"), +	EXYNOS_PIN_BANK_EINTG(0x040, EXYNOS4210_GPIO_B, "gpb"), +	EXYNOS_PIN_BANK_EINTG(0x060, EXYNOS4210_GPIO_C0, "gpc0"), +	EXYNOS_PIN_BANK_EINTG(0x080, EXYNOS4210_GPIO_C1, "gpc1"), +	EXYNOS_PIN_BANK_EINTG(0x0A0, EXYNOS4210_GPIO_D0, "gpd0"), +	EXYNOS_PIN_BANK_EINTG(0x0C0, EXYNOS4210_GPIO_D1, "gpd1"), +	EXYNOS_PIN_BANK_EINTG(0x0E0, EXYNOS4210_GPIO_E0, "gpe0"), +	EXYNOS_PIN_BANK_EINTG(0x100, EXYNOS4210_GPIO_E1, "gpe1"), +	EXYNOS_PIN_BANK_EINTG(0x120, EXYNOS4210_GPIO_E2, "gpe2"), +	EXYNOS_PIN_BANK_EINTG(0x140, EXYNOS4210_GPIO_E3, "gpe3"), +	EXYNOS_PIN_BANK_EINTG(0x160, EXYNOS4210_GPIO_E4, "gpe4"), +	EXYNOS_PIN_BANK_EINTG(0x180, EXYNOS4210_GPIO_F0, "gpf0"), +	EXYNOS_PIN_BANK_EINTG(0x1A0, EXYNOS4210_GPIO_F1, "gpf1"), +	EXYNOS_PIN_BANK_EINTG(0x1C0, EXYNOS4210_GPIO_F2, "gpf2"), +	EXYNOS_PIN_BANK_EINTG(0x1E0, EXYNOS4210_GPIO_F3, "gpf3"), +}; + +/* pin banks of exynos4210 pin-controller 1 */ +static struct samsung_pin_bank exynos4210_pin_banks1[] = { +	EXYNOS_PIN_BANK_EINTG(0x000, EXYNOS4210_GPIO_J0, "gpj0"), +	EXYNOS_PIN_BANK_EINTG(0x020, EXYNOS4210_GPIO_J1, "gpj1"), +	EXYNOS_PIN_BANK_EINTG(0x040, EXYNOS4210_GPIO_K0, "gpk0"), +	EXYNOS_PIN_BANK_EINTG(0x060, EXYNOS4210_GPIO_K1, "gpk1"), +	EXYNOS_PIN_BANK_EINTG(0x080, EXYNOS4210_GPIO_K2, "gpk2"), +	EXYNOS_PIN_BANK_EINTG(0x0A0, EXYNOS4210_GPIO_K3, "gpk3"), +	EXYNOS_PIN_BANK_EINTG(0x0C0, EXYNOS4210_GPIO_L0, "gpl0"), +	EXYNOS_PIN_BANK_EINTG(0x0E0, EXYNOS4210_GPIO_L1, "gpl1"), +	EXYNOS_PIN_BANK_EINTG(0x100, EXYNOS4210_GPIO_L2, "gpl2"), +	EXYNOS_PIN_BANK_EINTN(0x120, EXYNOS4210_GPIO_Y0, "gpy0"), +	EXYNOS_PIN_BANK_EINTN(0x140, EXYNOS4210_GPIO_Y1, "gpy1"), +	EXYNOS_PIN_BANK_EINTN(0x160, EXYNOS4210_GPIO_Y2, "gpy2"), +	EXYNOS_PIN_BANK_EINTN(0x180, EXYNOS4210_GPIO_Y3, "gpy3"), +	EXYNOS_PIN_BANK_EINTN(0x1A0, EXYNOS4210_GPIO_Y4, "gpy4"), +	EXYNOS_PIN_BANK_EINTN(0x1C0, EXYNOS4210_GPIO_Y5, "gpy5"), +	EXYNOS_PIN_BANK_EINTN(0x1E0, EXYNOS4210_GPIO_Y6, "gpy6"), +	EXYNOS_PIN_BANK_EINTN(0xC00, EXYNOS4210_GPIO_X0, "gpx0"), +	EXYNOS_PIN_BANK_EINTN(0xC20, EXYNOS4210_GPIO_X1, "gpx1"), +	EXYNOS_PIN_BANK_EINTN(0xC40, EXYNOS4210_GPIO_X2, "gpx2"), +	EXYNOS_PIN_BANK_EINTN(0xC60, EXYNOS4210_GPIO_X3, "gpx3"), +}; + +/* pin banks of exynos4210 pin-controller 2 */ +static struct samsung_pin_bank exynos4210_pin_banks2[] = { +	EXYNOS_PIN_BANK_EINTN(0x000, EXYNOS4210_GPIO_Z, "gpz"), +}; + +/* + * Samsung pinctrl driver data for Exynos4210 SoC. Exynos4210 SoC includes + * three gpio/pin-mux/pinconfig controllers. + */ +struct samsung_pin_ctrl exynos4210_pin_ctrl[] = { +	{ +		/* pin-controller instance 0 data */ +		.pin_banks	= exynos4210_pin_banks0, +		.nr_banks	= ARRAY_SIZE(exynos4210_pin_banks0), +		.base		= EXYNOS4210_GPIO_A0_START, +		.nr_pins	= EXYNOS4210_GPIOA_NR_PINS, +		.nr_gint	= EXYNOS4210_GPIOA_NR_GINT, +		.geint_con	= EXYNOS_GPIO_ECON_OFFSET, +		.geint_mask	= EXYNOS_GPIO_EMASK_OFFSET, +		.geint_pend	= EXYNOS_GPIO_EPEND_OFFSET, +		.svc		= EXYNOS_SVC_OFFSET, +		.eint_gpio_init = exynos_eint_gpio_init, +		.label		= "exynos4210-gpio-ctrl0", +	}, { +		/* pin-controller instance 1 data */ +		.pin_banks	= exynos4210_pin_banks1, +		.nr_banks	= ARRAY_SIZE(exynos4210_pin_banks1), +		.base		= EXYNOS4210_GPIOA_NR_PINS, +		.nr_pins	= EXYNOS4210_GPIOB_NR_PINS, +		.nr_gint	= EXYNOS4210_GPIOB_NR_GINT, +		.nr_wint	= 32, +		.geint_con	= EXYNOS_GPIO_ECON_OFFSET, +		.geint_mask	= EXYNOS_GPIO_EMASK_OFFSET, +		.geint_pend	= EXYNOS_GPIO_EPEND_OFFSET, +		.weint_con	= EXYNOS_WKUP_ECON_OFFSET, +		.weint_mask	= EXYNOS_WKUP_EMASK_OFFSET, +		.weint_pend	= EXYNOS_WKUP_EPEND_OFFSET, +		.svc		= EXYNOS_SVC_OFFSET, +		.eint_gpio_init = exynos_eint_gpio_init, +		.eint_wkup_init = exynos_eint_wkup_init, +		.label		= "exynos4210-gpio-ctrl1", +	}, { +		/* pin-controller instance 2 data */ +		.pin_banks	= exynos4210_pin_banks2, +		.nr_banks	= ARRAY_SIZE(exynos4210_pin_banks2), +		.base		= EXYNOS4210_GPIOA_NR_PINS + +					EXYNOS4210_GPIOB_NR_PINS, +		.nr_pins	= EXYNOS4210_GPIOC_NR_PINS, +		.label		= "exynos4210-gpio-ctrl2", +	}, +}; diff --git a/drivers/pinctrl/pinctrl-exynos.h b/drivers/pinctrl/pinctrl-exynos.h new file mode 100644 index 00000000000..31d0a06174e --- /dev/null +++ b/drivers/pinctrl/pinctrl-exynos.h @@ -0,0 +1,218 @@ +/* + * Exynos specific definitions for Samsung pinctrl and gpiolib driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + *		http://www.samsung.com + * Copyright (c) 2012 Linaro Ltd + *		http://www.linaro.org + * + * This file contains the Exynos specific definitions for the Samsung + * pinctrl/gpiolib interface drivers. + * + * Author: Thomas Abraham <thomas.ab@samsung.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; either version 2 of the License, or + * (at your option) any later version. + */ + +#define EXYNOS_GPIO_START(__gpio)	((__gpio##_START) + (__gpio##_NR)) + +#define EXYNOS4210_GPIO_A0_NR	(8) +#define EXYNOS4210_GPIO_A1_NR	(6) +#define EXYNOS4210_GPIO_B_NR	(8) +#define EXYNOS4210_GPIO_C0_NR	(5) +#define EXYNOS4210_GPIO_C1_NR	(5) +#define EXYNOS4210_GPIO_D0_NR	(4) +#define EXYNOS4210_GPIO_D1_NR	(4) +#define EXYNOS4210_GPIO_E0_NR	(5) +#define EXYNOS4210_GPIO_E1_NR	(8) +#define EXYNOS4210_GPIO_E2_NR	(6) +#define EXYNOS4210_GPIO_E3_NR	(8) +#define EXYNOS4210_GPIO_E4_NR	(8) +#define EXYNOS4210_GPIO_F0_NR	(8) +#define EXYNOS4210_GPIO_F1_NR	(8) +#define EXYNOS4210_GPIO_F2_NR	(8) +#define EXYNOS4210_GPIO_F3_NR	(6) +#define EXYNOS4210_GPIO_J0_NR	(8) +#define EXYNOS4210_GPIO_J1_NR	(5) +#define EXYNOS4210_GPIO_K0_NR	(7) +#define EXYNOS4210_GPIO_K1_NR	(7) +#define EXYNOS4210_GPIO_K2_NR	(7) +#define EXYNOS4210_GPIO_K3_NR	(7) +#define EXYNOS4210_GPIO_L0_NR	(8) +#define EXYNOS4210_GPIO_L1_NR	(3) +#define EXYNOS4210_GPIO_L2_NR	(8) +#define EXYNOS4210_GPIO_Y0_NR	(6) +#define EXYNOS4210_GPIO_Y1_NR	(4) +#define EXYNOS4210_GPIO_Y2_NR	(6) +#define EXYNOS4210_GPIO_Y3_NR	(8) +#define EXYNOS4210_GPIO_Y4_NR	(8) +#define EXYNOS4210_GPIO_Y5_NR	(8) +#define EXYNOS4210_GPIO_Y6_NR	(8) +#define EXYNOS4210_GPIO_X0_NR	(8) +#define EXYNOS4210_GPIO_X1_NR	(8) +#define EXYNOS4210_GPIO_X2_NR	(8) +#define EXYNOS4210_GPIO_X3_NR	(8) +#define EXYNOS4210_GPIO_Z_NR	(7) + +enum exynos4210_gpio_xa_start { +	EXYNOS4210_GPIO_A0_START	= 0, +	EXYNOS4210_GPIO_A1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_A0), +	EXYNOS4210_GPIO_B_START		= EXYNOS_GPIO_START(EXYNOS4210_GPIO_A1), +	EXYNOS4210_GPIO_C0_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_B), +	EXYNOS4210_GPIO_C1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_C0), +	EXYNOS4210_GPIO_D0_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_C1), +	EXYNOS4210_GPIO_D1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_D0), +	EXYNOS4210_GPIO_E0_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_D1), +	EXYNOS4210_GPIO_E1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_E0), +	EXYNOS4210_GPIO_E2_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_E1), +	EXYNOS4210_GPIO_E3_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_E2), +	EXYNOS4210_GPIO_E4_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_E3), +	EXYNOS4210_GPIO_F0_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_E4), +	EXYNOS4210_GPIO_F1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_F0), +	EXYNOS4210_GPIO_F2_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_F1), +	EXYNOS4210_GPIO_F3_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_F2), +}; + +enum exynos4210_gpio_xb_start { +	EXYNOS4210_GPIO_J0_START	= 0, +	EXYNOS4210_GPIO_J1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_J0), +	EXYNOS4210_GPIO_K0_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_J1), +	EXYNOS4210_GPIO_K1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_K0), +	EXYNOS4210_GPIO_K2_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_K1), +	EXYNOS4210_GPIO_K3_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_K2), +	EXYNOS4210_GPIO_L0_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_K3), +	EXYNOS4210_GPIO_L1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_L0), +	EXYNOS4210_GPIO_L2_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_L1), +	EXYNOS4210_GPIO_Y0_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_L2), +	EXYNOS4210_GPIO_Y1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_Y0), +	EXYNOS4210_GPIO_Y2_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_Y1), +	EXYNOS4210_GPIO_Y3_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_Y2), +	EXYNOS4210_GPIO_Y4_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_Y3), +	EXYNOS4210_GPIO_Y5_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_Y4), +	EXYNOS4210_GPIO_Y6_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_Y5), +	EXYNOS4210_GPIO_X0_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_Y6), +	EXYNOS4210_GPIO_X1_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_X0), +	EXYNOS4210_GPIO_X2_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_X1), +	EXYNOS4210_GPIO_X3_START	= EXYNOS_GPIO_START(EXYNOS4210_GPIO_X2), +}; + +enum exynos4210_gpio_xc_start { +	EXYNOS4210_GPIO_Z_START		= 0, +}; + +#define	EXYNOS4210_GPIO_A0_IRQ		EXYNOS4210_GPIO_A0_START +#define	EXYNOS4210_GPIO_A1_IRQ		EXYNOS4210_GPIO_A1_START +#define	EXYNOS4210_GPIO_B_IRQ		EXYNOS4210_GPIO_B_START +#define	EXYNOS4210_GPIO_C0_IRQ		EXYNOS4210_GPIO_C0_START +#define	EXYNOS4210_GPIO_C1_IRQ		EXYNOS4210_GPIO_C1_START +#define	EXYNOS4210_GPIO_D0_IRQ		EXYNOS4210_GPIO_D0_START +#define	EXYNOS4210_GPIO_D1_IRQ		EXYNOS4210_GPIO_D1_START +#define	EXYNOS4210_GPIO_E0_IRQ		EXYNOS4210_GPIO_E0_START +#define	EXYNOS4210_GPIO_E1_IRQ		EXYNOS4210_GPIO_E1_START +#define	EXYNOS4210_GPIO_E2_IRQ		EXYNOS4210_GPIO_E2_START +#define	EXYNOS4210_GPIO_E3_IRQ		EXYNOS4210_GPIO_E3_START +#define	EXYNOS4210_GPIO_E4_IRQ		EXYNOS4210_GPIO_E4_START +#define	EXYNOS4210_GPIO_F0_IRQ		EXYNOS4210_GPIO_F0_START +#define	EXYNOS4210_GPIO_F1_IRQ		EXYNOS4210_GPIO_F1_START +#define	EXYNOS4210_GPIO_F2_IRQ		EXYNOS4210_GPIO_F2_START +#define	EXYNOS4210_GPIO_F3_IRQ		EXYNOS4210_GPIO_F3_START +#define	EXYNOS4210_GPIO_J0_IRQ		EXYNOS4210_GPIO_J0_START +#define	EXYNOS4210_GPIO_J1_IRQ		EXYNOS4210_GPIO_J1_START +#define	EXYNOS4210_GPIO_K0_IRQ		EXYNOS4210_GPIO_K0_START +#define	EXYNOS4210_GPIO_K1_IRQ		EXYNOS4210_GPIO_K1_START +#define	EXYNOS4210_GPIO_K2_IRQ		EXYNOS4210_GPIO_K2_START +#define	EXYNOS4210_GPIO_K3_IRQ		EXYNOS4210_GPIO_K3_START +#define	EXYNOS4210_GPIO_L0_IRQ		EXYNOS4210_GPIO_L0_START +#define	EXYNOS4210_GPIO_L1_IRQ		EXYNOS4210_GPIO_L1_START +#define	EXYNOS4210_GPIO_L2_IRQ		EXYNOS4210_GPIO_L2_START +#define	EXYNOS4210_GPIO_Z_IRQ		EXYNOS4210_GPIO_Z_START + +#define EXYNOS4210_GPIOA_NR_PINS	EXYNOS_GPIO_START(EXYNOS4210_GPIO_F3) +#define EXYNOS4210_GPIOA_NR_GINT	EXYNOS_GPIO_START(EXYNOS4210_GPIO_F3) +#define EXYNOS4210_GPIOB_NR_PINS	EXYNOS_GPIO_START(EXYNOS4210_GPIO_X3) +#define EXYNOS4210_GPIOB_NR_GINT	EXYNOS_GPIO_START(EXYNOS4210_GPIO_L2) +#define EXYNOS4210_GPIOC_NR_PINS	EXYNOS_GPIO_START(EXYNOS4210_GPIO_Z) + +/* External GPIO and wakeup interrupt related definitions */ +#define EXYNOS_GPIO_ECON_OFFSET		0x700 +#define EXYNOS_GPIO_EMASK_OFFSET	0x900 +#define EXYNOS_GPIO_EPEND_OFFSET	0xA00 +#define EXYNOS_WKUP_ECON_OFFSET		0xE00 +#define EXYNOS_WKUP_EMASK_OFFSET	0xF00 +#define EXYNOS_WKUP_EPEND_OFFSET	0xF40 +#define EXYNOS_SVC_OFFSET		0xB08 +#define EXYNOS_EINT_FUNC		0xF + +/* helpers to access interrupt service register */ +#define EXYNOS_SVC_GROUP_SHIFT		3 +#define EXYNOS_SVC_GROUP_MASK		0x1f +#define EXYNOS_SVC_NUM_MASK		7 +#define EXYNOS_SVC_GROUP(x)		((x >> EXYNOS_SVC_GROUP_SHIFT) & \ +						EXYNOS_SVC_GROUP_MASK) + +/* Exynos specific external interrupt trigger types */ +#define EXYNOS_EINT_LEVEL_LOW		0 +#define EXYNOS_EINT_LEVEL_HIGH		1 +#define EXYNOS_EINT_EDGE_FALLING	2 +#define EXYNOS_EINT_EDGE_RISING		3 +#define EXYNOS_EINT_EDGE_BOTH		4 +#define EXYNOS_EINT_CON_MASK		0xF +#define EXYNOS_EINT_CON_LEN		4 + +#define EXYNOS_EINT_MAX_PER_BANK	8 +#define EXYNOS_EINT_NR_WKUP_EINT + +#define EXYNOS_PIN_BANK_EINTN(reg, __gpio, id)		\ +	{						\ +		.pctl_offset	= reg,			\ +		.pin_base	= (__gpio##_START),	\ +		.nr_pins	= (__gpio##_NR),	\ +		.func_width	= 4,			\ +		.pud_width	= 2,			\ +		.drv_width	= 2,			\ +		.conpdn_width	= 2,			\ +		.pudpdn_width	= 2,			\ +		.eint_type	= EINT_TYPE_NONE,	\ +		.name		= id			\ +	} + +#define EXYNOS_PIN_BANK_EINTG(reg, __gpio, id)		\ +	{						\ +		.pctl_offset	= reg,			\ +		.pin_base	= (__gpio##_START),	\ +		.nr_pins	= (__gpio##_NR),	\ +		.func_width	= 4,			\ +		.pud_width	= 2,			\ +		.drv_width	= 2,			\ +		.conpdn_width	= 2,			\ +		.pudpdn_width	= 2,			\ +		.eint_type	= EINT_TYPE_GPIO,	\ +		.irq_base	= (__gpio##_IRQ),	\ +		.name		= id			\ +	} + +/** + * struct exynos_geint_data: gpio eint specific data for irq_chip callbacks. + * @bank: pin bank from which this gpio interrupt originates. + * @pin: pin number within the bank. + * @eint_offset: offset to be added to the con/pend/mask register bank base. + */ +struct exynos_geint_data { +	struct samsung_pin_bank	*bank; +	u32			pin; +	u32			eint_offset; +}; + +/** + * struct exynos_weint_data: irq specific data for all the wakeup interrupts + * generated by the external wakeup interrupt controller. + * @domain: irq domain representing the external wakeup interrupts + * @irq: interrupt number within the domain. + */ +struct exynos_weint_data { +	struct irq_domain	*domain; +	u32			irq; +}; diff --git a/drivers/pinctrl/pinctrl-samsung.c b/drivers/pinctrl/pinctrl-samsung.c new file mode 100644 index 00000000000..dd108a94acf --- /dev/null +++ b/drivers/pinctrl/pinctrl-samsung.c @@ -0,0 +1,888 @@ +/* + * pin-controller/pin-mux/pin-config/gpio-driver for Samsung's SoC's. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + *		http://www.samsung.com + * Copyright (c) 2012 Linaro Ltd + *		http://www.linaro.org + * + * Author: Thomas Abraham <thomas.ab@samsung.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; either version 2 of the License, or + * (at your option) any later version. + * + * This driver implements the Samsung pinctrl driver. It supports setting up of + * pinmux and pinconf configurations. The gpiolib interface is also included. + * External interrupt (gpio and wakeup) support are not included in this driver + * but provides extensions to which platform specific implementation of the gpio + * and wakeup interrupts can be hooked to. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/gpio.h> + +#include "core.h" +#include "pinctrl-samsung.h" + +#define GROUP_SUFFIX		"-grp" +#define GSUFFIX_LEN		sizeof(GROUP_SUFFIX) +#define FUNCTION_SUFFIX		"-mux" +#define FSUFFIX_LEN		sizeof(FUNCTION_SUFFIX) + +/* list of all possible config options supported */ +struct pin_config { +	char		*prop_cfg; +	unsigned int	cfg_type; +} pcfgs[] = { +	{ "samsung,pin-pud", PINCFG_TYPE_PUD }, +	{ "samsung,pin-drv", PINCFG_TYPE_DRV }, +	{ "samsung,pin-con-pdn", PINCFG_TYPE_CON_PDN }, +	{ "samsung,pin-pud-pdn", PINCFG_TYPE_PUD_PDN }, +}; + +/* check if the selector is a valid pin group selector */ +static int samsung_get_group_count(struct pinctrl_dev *pctldev) +{ +	struct samsung_pinctrl_drv_data *drvdata; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	return drvdata->nr_groups; +} + +/* return the name of the group selected by the group selector */ +static const char *samsung_get_group_name(struct pinctrl_dev *pctldev, +						unsigned selector) +{ +	struct samsung_pinctrl_drv_data *drvdata; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	return drvdata->pin_groups[selector].name; +} + +/* return the pin numbers associated with the specified group */ +static int samsung_get_group_pins(struct pinctrl_dev *pctldev, +		unsigned selector, const unsigned **pins, unsigned *num_pins) +{ +	struct samsung_pinctrl_drv_data *drvdata; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	*pins = drvdata->pin_groups[selector].pins; +	*num_pins = drvdata->pin_groups[selector].num_pins; +	return 0; +} + +/* create pinctrl_map entries by parsing device tree nodes */ +static int samsung_dt_node_to_map(struct pinctrl_dev *pctldev, +			struct device_node *np, struct pinctrl_map **maps, +			unsigned *nmaps) +{ +	struct device *dev = pctldev->dev; +	struct pinctrl_map *map; +	unsigned long *cfg = NULL; +	char *gname, *fname; +	int cfg_cnt = 0, map_cnt = 0, idx = 0; + +	/* count the number of config options specfied in the node */ +	for (idx = 0; idx < ARRAY_SIZE(pcfgs); idx++) { +		if (of_find_property(np, pcfgs[idx].prop_cfg, NULL)) +			cfg_cnt++; +	} + +	/* +	 * Find out the number of map entries to create. All the config options +	 * can be accomadated into a single config map entry. +	 */ +	if (cfg_cnt) +		map_cnt = 1; +	if (of_find_property(np, "samsung,pin-function", NULL)) +		map_cnt++; +	if (!map_cnt) { +		dev_err(dev, "node %s does not have either config or function " +				"configurations\n", np->name); +		return -EINVAL; +	} + +	/* Allocate memory for pin-map entries */ +	map = kzalloc(sizeof(*map) * map_cnt, GFP_KERNEL); +	if (!map) { +		dev_err(dev, "could not alloc memory for pin-maps\n"); +		return -ENOMEM; +	} +	*nmaps = 0; + +	/* +	 * Allocate memory for pin group name. The pin group name is derived +	 * from the node name from which these map entries are be created. +	 */ +	gname = kzalloc(strlen(np->name) + GSUFFIX_LEN, GFP_KERNEL); +	if (!gname) { +		dev_err(dev, "failed to alloc memory for group name\n"); +		goto free_map; +	} +	sprintf(gname, "%s%s", np->name, GROUP_SUFFIX); + +	/* +	 * don't have config options? then skip over to creating function +	 * map entries. +	 */ +	if (!cfg_cnt) +		goto skip_cfgs; + +	/* Allocate memory for config entries */ +	cfg = kzalloc(sizeof(*cfg) * cfg_cnt, GFP_KERNEL); +	if (!cfg) { +		dev_err(dev, "failed to alloc memory for configs\n"); +		goto free_gname; +	} + +	/* Prepare a list of config settings */ +	for (idx = 0, cfg_cnt = 0; idx < ARRAY_SIZE(pcfgs); idx++) { +		u32 value; +		if (!of_property_read_u32(np, pcfgs[idx].prop_cfg, &value)) +			cfg[cfg_cnt++] = +				PINCFG_PACK(pcfgs[idx].cfg_type, value); +	} + +	/* create the config map entry */ +	map[*nmaps].data.configs.group_or_pin = gname; +	map[*nmaps].data.configs.configs = cfg; +	map[*nmaps].data.configs.num_configs = cfg_cnt; +	map[*nmaps].type = PIN_MAP_TYPE_CONFIGS_GROUP; +	*nmaps += 1; + +skip_cfgs: +	/* create the function map entry */ +	if (of_find_property(np, "samsung,pin-function", NULL)) { +		fname = kzalloc(strlen(np->name) + FSUFFIX_LEN,	GFP_KERNEL); +		if (!fname) { +			dev_err(dev, "failed to alloc memory for func name\n"); +			goto free_cfg; +		} +		sprintf(fname, "%s%s", np->name, FUNCTION_SUFFIX); + +		map[*nmaps].data.mux.group = gname; +		map[*nmaps].data.mux.function = fname; +		map[*nmaps].type = PIN_MAP_TYPE_MUX_GROUP; +		*nmaps += 1; +	} + +	*maps = map; +	return 0; + +free_cfg: +	kfree(cfg); +free_gname: +	kfree(gname); +free_map: +	kfree(map); +	return -ENOMEM; +} + +/* free the memory allocated to hold the pin-map table */ +static void samsung_dt_free_map(struct pinctrl_dev *pctldev, +			     struct pinctrl_map *map, unsigned num_maps) +{ +	int idx; + +	for (idx = 0; idx < num_maps; idx++) { +		if (map[idx].type == PIN_MAP_TYPE_MUX_GROUP) { +			kfree(map[idx].data.mux.function); +			if (!idx) +				kfree(map[idx].data.mux.group); +		} else if (map->type == PIN_MAP_TYPE_CONFIGS_GROUP) { +			kfree(map[idx].data.configs.configs); +			if (!idx) +				kfree(map[idx].data.configs.group_or_pin); +		} +	}; + +	kfree(map); +} + +/* list of pinctrl callbacks for the pinctrl core */ +static struct pinctrl_ops samsung_pctrl_ops = { +	.get_groups_count	= samsung_get_group_count, +	.get_group_name		= samsung_get_group_name, +	.get_group_pins		= samsung_get_group_pins, +	.dt_node_to_map		= samsung_dt_node_to_map, +	.dt_free_map		= samsung_dt_free_map, +}; + +/* check if the selector is a valid pin function selector */ +static int samsung_get_functions_count(struct pinctrl_dev *pctldev) +{ +	struct samsung_pinctrl_drv_data *drvdata; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	return drvdata->nr_functions; +} + +/* return the name of the pin function specified */ +static const char *samsung_pinmux_get_fname(struct pinctrl_dev *pctldev, +						unsigned selector) +{ +	struct samsung_pinctrl_drv_data *drvdata; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	return drvdata->pmx_functions[selector].name; +} + +/* return the groups associated for the specified function selector */ +static int samsung_pinmux_get_groups(struct pinctrl_dev *pctldev, +		unsigned selector, const char * const **groups, +		unsigned * const num_groups) +{ +	struct samsung_pinctrl_drv_data *drvdata; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	*groups = drvdata->pmx_functions[selector].groups; +	*num_groups = drvdata->pmx_functions[selector].num_groups; +	return 0; +} + +/* + * given a pin number that is local to a pin controller, find out the pin bank + * and the register base of the pin bank. + */ +static void pin_to_reg_bank(struct gpio_chip *gc, unsigned pin, +			void __iomem **reg, u32 *offset, +			struct samsung_pin_bank **bank) +{ +	struct samsung_pinctrl_drv_data *drvdata; +	struct samsung_pin_bank *b; + +	drvdata = dev_get_drvdata(gc->dev); +	b = drvdata->ctrl->pin_banks; + +	while ((pin >= b->pin_base) && +			((b->pin_base + b->nr_pins - 1) < pin)) +		b++; + +	*reg = drvdata->virt_base + b->pctl_offset; +	*offset = pin - b->pin_base; +	if (bank) +		*bank = b; + +	/* some banks have two config registers in a single bank */ +	if (*offset * b->func_width > BITS_PER_LONG) +		*reg += 4; +} + +/* enable or disable a pinmux function */ +static void samsung_pinmux_setup(struct pinctrl_dev *pctldev, unsigned selector, +					unsigned group, bool enable) +{ +	struct samsung_pinctrl_drv_data *drvdata; +	const unsigned int *pins; +	struct samsung_pin_bank *bank; +	void __iomem *reg; +	u32 mask, shift, data, pin_offset, cnt; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	pins = drvdata->pin_groups[group].pins; + +	/* +	 * for each pin in the pin group selected, program the correspoding pin +	 * pin function number in the config register. +	 */ +	for (cnt = 0; cnt < drvdata->pin_groups[group].num_pins; cnt++) { +		pin_to_reg_bank(drvdata->gc, pins[cnt] - drvdata->ctrl->base, +				®, &pin_offset, &bank); +		mask = (1 << bank->func_width) - 1; +		shift = pin_offset * bank->func_width; + +		data = readl(reg); +		data &= ~(mask << shift); +		if (enable) +			data |= drvdata->pin_groups[group].func << shift; +		writel(data, reg); +	} +} + +/* enable a specified pinmux by writing to registers */ +static int samsung_pinmux_enable(struct pinctrl_dev *pctldev, unsigned selector, +					unsigned group) +{ +	samsung_pinmux_setup(pctldev, selector, group, true); +	return 0; +} + +/* disable a specified pinmux by writing to registers */ +static void samsung_pinmux_disable(struct pinctrl_dev *pctldev, +					unsigned selector, unsigned group) +{ +	samsung_pinmux_setup(pctldev, selector, group, false); +} + +/* + * The calls to gpio_direction_output() and gpio_direction_input() + * leads to this function call (via the pinctrl_gpio_direction_{input|output}() + * function called from the gpiolib interface). + */ +static int samsung_pinmux_gpio_set_direction(struct pinctrl_dev *pctldev, +		struct pinctrl_gpio_range *range, unsigned offset, bool input) +{ +	struct samsung_pin_bank *bank; +	void __iomem *reg; +	u32 data, pin_offset, mask, shift; + +	pin_to_reg_bank(range->gc, offset, ®, &pin_offset, &bank); +	mask = (1 << bank->func_width) - 1; +	shift = pin_offset * bank->func_width; + +	data = readl(reg); +	data &= ~(mask << shift); +	if (!input) +		data |= FUNC_OUTPUT << shift; +	writel(data, reg); +	return 0; +} + +/* list of pinmux callbacks for the pinmux vertical in pinctrl core */ +static struct pinmux_ops samsung_pinmux_ops = { +	.get_functions_count	= samsung_get_functions_count, +	.get_function_name	= samsung_pinmux_get_fname, +	.get_function_groups	= samsung_pinmux_get_groups, +	.enable			= samsung_pinmux_enable, +	.disable		= samsung_pinmux_disable, +	.gpio_set_direction	= samsung_pinmux_gpio_set_direction, +}; + +/* set or get the pin config settings for a specified pin */ +static int samsung_pinconf_rw(struct pinctrl_dev *pctldev, unsigned int pin, +				unsigned long *config, bool set) +{ +	struct samsung_pinctrl_drv_data *drvdata; +	struct samsung_pin_bank *bank; +	void __iomem *reg_base; +	enum pincfg_type cfg_type = PINCFG_UNPACK_TYPE(*config); +	u32 data, width, pin_offset, mask, shift; +	u32 cfg_value, cfg_reg; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	pin_to_reg_bank(drvdata->gc, pin - drvdata->ctrl->base, ®_base, +					&pin_offset, &bank); + +	switch (cfg_type) { +	case PINCFG_TYPE_PUD: +		width = bank->pud_width; +		cfg_reg = PUD_REG; +		break; +	case PINCFG_TYPE_DRV: +		width = bank->drv_width; +		cfg_reg = DRV_REG; +		break; +	case PINCFG_TYPE_CON_PDN: +		width = bank->conpdn_width; +		cfg_reg = CONPDN_REG; +		break; +	case PINCFG_TYPE_PUD_PDN: +		width = bank->pudpdn_width; +		cfg_reg = PUDPDN_REG; +		break; +	default: +		WARN_ON(1); +		return -EINVAL; +	} + +	mask = (1 << width) - 1; +	shift = pin_offset * width; +	data = readl(reg_base + cfg_reg); + +	if (set) { +		cfg_value = PINCFG_UNPACK_VALUE(*config); +		data &= ~(mask << shift); +		data |= (cfg_value << shift); +		writel(data, reg_base + cfg_reg); +	} else { +		data >>= shift; +		data &= mask; +		*config = PINCFG_PACK(cfg_type, data); +	} +	return 0; +} + +/* set the pin config settings for a specified pin */ +static int samsung_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin, +				unsigned long config) +{ +	return samsung_pinconf_rw(pctldev, pin, &config, true); +} + +/* get the pin config settings for a specified pin */ +static int samsung_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin, +					unsigned long *config) +{ +	return samsung_pinconf_rw(pctldev, pin, config, false); +} + +/* set the pin config settings for a specified pin group */ +static int samsung_pinconf_group_set(struct pinctrl_dev *pctldev, +			unsigned group, unsigned long config) +{ +	struct samsung_pinctrl_drv_data *drvdata; +	const unsigned int *pins; +	unsigned int cnt; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	pins = drvdata->pin_groups[group].pins; + +	for (cnt = 0; cnt < drvdata->pin_groups[group].num_pins; cnt++) +		samsung_pinconf_set(pctldev, pins[cnt], config); + +	return 0; +} + +/* get the pin config settings for a specified pin group */ +static int samsung_pinconf_group_get(struct pinctrl_dev *pctldev, +				unsigned int group, unsigned long *config) +{ +	struct samsung_pinctrl_drv_data *drvdata; +	const unsigned int *pins; + +	drvdata = pinctrl_dev_get_drvdata(pctldev); +	pins = drvdata->pin_groups[group].pins; +	samsung_pinconf_get(pctldev, pins[0], config); +	return 0; +} + +/* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */ +static struct pinconf_ops samsung_pinconf_ops = { +	.pin_config_get		= samsung_pinconf_get, +	.pin_config_set		= samsung_pinconf_set, +	.pin_config_group_get	= samsung_pinconf_group_get, +	.pin_config_group_set	= samsung_pinconf_group_set, +}; + +/* gpiolib gpio_set callback function */ +static void samsung_gpio_set(struct gpio_chip *gc, unsigned offset, int value) +{ +	void __iomem *reg; +	u32 pin_offset, data; + +	pin_to_reg_bank(gc, offset, ®, &pin_offset, NULL); +	data = readl(reg + DAT_REG); +	data &= ~(1 << pin_offset); +	if (value) +		data |= 1 << pin_offset; +	writel(data, reg + DAT_REG); +} + +/* gpiolib gpio_get callback function */ +static int samsung_gpio_get(struct gpio_chip *gc, unsigned offset) +{ +	void __iomem *reg; +	u32 pin_offset, data; + +	pin_to_reg_bank(gc, offset, ®, &pin_offset, NULL); +	data = readl(reg + DAT_REG); +	data >>= pin_offset; +	data &= 1; +	return data; +} + +/* + * gpiolib gpio_direction_input callback function. The setting of the pin + * mux function as 'gpio input' will be handled by the pinctrl susbsystem + * interface. + */ +static int samsung_gpio_direction_input(struct gpio_chip *gc, unsigned offset) +{ +	return pinctrl_gpio_direction_input(gc->base + offset); +} + +/* + * gpiolib gpio_direction_output callback function. The setting of the pin + * mux function as 'gpio output' will be handled by the pinctrl susbsystem + * interface. + */ +static int samsung_gpio_direction_output(struct gpio_chip *gc, unsigned offset, +							int value) +{ +	samsung_gpio_set(gc, offset, value); +	return pinctrl_gpio_direction_output(gc->base + offset); +} + +/* + * Parse the pin names listed in the 'samsung,pins' property and convert it + * into a list of gpio numbers are create a pin group from it. + */ +static int __init samsung_pinctrl_parse_dt_pins(struct platform_device *pdev, +			struct device_node *cfg_np, struct pinctrl_desc *pctl, +			unsigned int **pin_list, unsigned int *npins) +{ +	struct device *dev = &pdev->dev; +	struct property *prop; +	struct pinctrl_pin_desc const *pdesc = pctl->pins; +	unsigned int idx = 0, cnt; +	const char *pin_name; + +	*npins = of_property_count_strings(cfg_np, "samsung,pins"); +	if (*npins < 0) { +		dev_err(dev, "invalid pin list in %s node", cfg_np->name); +		return -EINVAL; +	} + +	*pin_list = devm_kzalloc(dev, *npins * sizeof(**pin_list), GFP_KERNEL); +	if (!*pin_list) { +		dev_err(dev, "failed to allocate memory for pin list\n"); +		return -ENOMEM; +	} + +	of_property_for_each_string(cfg_np, "samsung,pins", prop, pin_name) { +		for (cnt = 0; cnt < pctl->npins; cnt++) { +			if (pdesc[cnt].name) { +				if (!strcmp(pin_name, pdesc[cnt].name)) { +					(*pin_list)[idx++] = pdesc[cnt].number; +					break; +				} +			} +		} +		if (cnt == pctl->npins) { +			dev_err(dev, "pin %s not valid in %s node\n", +					pin_name, cfg_np->name); +			devm_kfree(dev, *pin_list); +			return -EINVAL; +		} +	} + +	return 0; +} + +/* + * Parse the information about all the available pin groups and pin functions + * from device node of the pin-controller. A pin group is formed with all + * the pins listed in the "samsung,pins" property. + */ +static int __init samsung_pinctrl_parse_dt(struct platform_device *pdev, +				struct samsung_pinctrl_drv_data *drvdata) +{ +	struct device *dev = &pdev->dev; +	struct device_node *dev_np = dev->of_node; +	struct device_node *cfg_np; +	struct samsung_pin_group *groups, *grp; +	struct samsung_pmx_func *functions, *func; +	unsigned *pin_list; +	unsigned int npins, grp_cnt, func_idx = 0; +	char *gname, *fname; +	int ret; + +	grp_cnt = of_get_child_count(dev_np); +	if (!grp_cnt) +		return -EINVAL; + +	groups = devm_kzalloc(dev, grp_cnt * sizeof(*groups), GFP_KERNEL); +	if (!groups) { +		dev_err(dev, "failed allocate memory for ping group list\n"); +		return -EINVAL; +	} +	grp = groups; + +	functions = devm_kzalloc(dev, grp_cnt * sizeof(*functions), GFP_KERNEL); +	if (!functions) { +		dev_err(dev, "failed to allocate memory for function list\n"); +		return -EINVAL; +	} +	func = functions; + +	/* +	 * Iterate over all the child nodes of the pin controller node +	 * and create pin groups and pin function lists. +	 */ +	for_each_child_of_node(dev_np, cfg_np) { +		u32 function; +		if (of_find_property(cfg_np, "interrupt-controller", NULL)) +			continue; + +		ret = samsung_pinctrl_parse_dt_pins(pdev, cfg_np, +					&drvdata->pctl,	&pin_list, &npins); +		if (ret) +			return ret; + +		/* derive pin group name from the node name */ +		gname = devm_kzalloc(dev, strlen(cfg_np->name) + GSUFFIX_LEN, +					GFP_KERNEL); +		if (!gname) { +			dev_err(dev, "failed to alloc memory for group name\n"); +			return -ENOMEM; +		} +		sprintf(gname, "%s%s", cfg_np->name, GROUP_SUFFIX); + +		grp->name = gname; +		grp->pins = pin_list; +		grp->num_pins = npins; +		of_property_read_u32(cfg_np, "samsung,pin-function", &function); +		grp->func = function; +		grp++; + +		if (!of_find_property(cfg_np, "samsung,pin-function", NULL)) +			continue; + +		/* derive function name from the node name */ +		fname = devm_kzalloc(dev, strlen(cfg_np->name) + FSUFFIX_LEN, +					GFP_KERNEL); +		if (!fname) { +			dev_err(dev, "failed to alloc memory for func name\n"); +			return -ENOMEM; +		} +		sprintf(fname, "%s%s", cfg_np->name, FUNCTION_SUFFIX); + +		func->name = fname; +		func->groups = devm_kzalloc(dev, sizeof(char *), GFP_KERNEL); +		if (!func->groups) { +			dev_err(dev, "failed to alloc memory for group list " +					"in pin function"); +			return -ENOMEM; +		} +		func->groups[0] = gname; +		func->num_groups = 1; +		func++; +		func_idx++; +	} + +	drvdata->pin_groups = groups; +	drvdata->nr_groups = grp_cnt; +	drvdata->pmx_functions = functions; +	drvdata->nr_functions = func_idx; + +	return 0; +} + +/* register the pinctrl interface with the pinctrl subsystem */ +static int __init samsung_pinctrl_register(struct platform_device *pdev, +				struct samsung_pinctrl_drv_data *drvdata) +{ +	struct pinctrl_desc *ctrldesc = &drvdata->pctl; +	struct pinctrl_pin_desc *pindesc, *pdesc; +	struct samsung_pin_bank *pin_bank; +	char *pin_names; +	int pin, bank, ret; + +	ctrldesc->name = "samsung-pinctrl"; +	ctrldesc->owner = THIS_MODULE; +	ctrldesc->pctlops = &samsung_pctrl_ops; +	ctrldesc->pmxops = &samsung_pinmux_ops; +	ctrldesc->confops = &samsung_pinconf_ops; + +	pindesc = devm_kzalloc(&pdev->dev, sizeof(*pindesc) * +			drvdata->ctrl->nr_pins, GFP_KERNEL); +	if (!pindesc) { +		dev_err(&pdev->dev, "mem alloc for pin descriptors failed\n"); +		return -ENOMEM; +	} +	ctrldesc->pins = pindesc; +	ctrldesc->npins = drvdata->ctrl->nr_pins; +	ctrldesc->npins = drvdata->ctrl->nr_pins; + +	/* dynamically populate the pin number and pin name for pindesc */ +	for (pin = 0, pdesc = pindesc; pin < ctrldesc->npins; pin++, pdesc++) +		pdesc->number = pin + drvdata->ctrl->base; + +	/* +	 * allocate space for storing the dynamically generated names for all +	 * the pins which belong to this pin-controller. +	 */ +	pin_names = devm_kzalloc(&pdev->dev, sizeof(char) * PIN_NAME_LENGTH * +					drvdata->ctrl->nr_pins, GFP_KERNEL); +	if (!pin_names) { +		dev_err(&pdev->dev, "mem alloc for pin names failed\n"); +		return -ENOMEM; +	} + +	/* for each pin, the name of the pin is pin-bank name + pin number */ +	for (bank = 0; bank < drvdata->ctrl->nr_banks; bank++) { +		pin_bank = &drvdata->ctrl->pin_banks[bank]; +		for (pin = 0; pin < pin_bank->nr_pins; pin++) { +			sprintf(pin_names, "%s-%d", pin_bank->name, pin); +			pdesc = pindesc + pin_bank->pin_base + pin; +			pdesc->name = pin_names; +			pin_names += PIN_NAME_LENGTH; +		} +	} + +	drvdata->pctl_dev = pinctrl_register(ctrldesc, &pdev->dev, drvdata); +	if (!drvdata->pctl_dev) { +		dev_err(&pdev->dev, "could not register pinctrl driver\n"); +		return -EINVAL; +	} + +	drvdata->grange.name = "samsung-pctrl-gpio-range"; +	drvdata->grange.id = 0; +	drvdata->grange.base = drvdata->ctrl->base; +	drvdata->grange.npins = drvdata->ctrl->nr_pins; +	drvdata->grange.gc = drvdata->gc; +	pinctrl_add_gpio_range(drvdata->pctl_dev, &drvdata->grange); + +	ret = samsung_pinctrl_parse_dt(pdev, drvdata); +	if (ret) { +		pinctrl_unregister(drvdata->pctl_dev); +		return ret; +	} + +	return 0; +} + +/* register the gpiolib interface with the gpiolib subsystem */ +static int __init samsung_gpiolib_register(struct platform_device *pdev, +				struct samsung_pinctrl_drv_data *drvdata) +{ +	struct gpio_chip *gc; +	int ret; + +	gc = devm_kzalloc(&pdev->dev, sizeof(*gc), GFP_KERNEL); +	if (!gc) { +		dev_err(&pdev->dev, "mem alloc for gpio_chip failed\n"); +		return -ENOMEM; +	} + +	drvdata->gc = gc; +	gc->base = drvdata->ctrl->base; +	gc->ngpio = drvdata->ctrl->nr_pins; +	gc->dev = &pdev->dev; +	gc->set = samsung_gpio_set; +	gc->get = samsung_gpio_get; +	gc->direction_input = samsung_gpio_direction_input; +	gc->direction_output = samsung_gpio_direction_output; +	gc->label = drvdata->ctrl->label; +	gc->owner = THIS_MODULE; +	ret = gpiochip_add(gc); +	if (ret) { +		dev_err(&pdev->dev, "failed to register gpio_chip %s, error " +					"code: %d\n", gc->label, ret); +		return ret; +	} + +	return 0; +} + +/* unregister the gpiolib interface with the gpiolib subsystem */ +static int __init samsung_gpiolib_unregister(struct platform_device *pdev, +				struct samsung_pinctrl_drv_data *drvdata) +{ +	int ret = gpiochip_remove(drvdata->gc); +	if (ret) { +		dev_err(&pdev->dev, "gpio chip remove failed\n"); +		return ret; +	} +	return 0; +} + +static const struct of_device_id samsung_pinctrl_dt_match[]; + +/* retrieve the soc specific data */ +static struct samsung_pin_ctrl *samsung_pinctrl_get_soc_data( +				struct platform_device *pdev) +{ +	int id; +	const struct of_device_id *match; +	const struct device_node *node = pdev->dev.of_node; + +	id = of_alias_get_id(pdev->dev.of_node, "pinctrl"); +	if (id < 0) { +		dev_err(&pdev->dev, "failed to get alias id\n"); +		return NULL; +	} +	match = of_match_node(samsung_pinctrl_dt_match, node); +	return (struct samsung_pin_ctrl *)match->data + id; +} + +static int __devinit samsung_pinctrl_probe(struct platform_device *pdev) +{ +	struct samsung_pinctrl_drv_data *drvdata; +	struct device *dev = &pdev->dev; +	struct samsung_pin_ctrl *ctrl; +	struct resource *res; +	int ret; + +	if (!dev->of_node) { +		dev_err(dev, "device tree node not found\n"); +		return -ENODEV; +	} + +	ctrl = samsung_pinctrl_get_soc_data(pdev); +	if (!ctrl) { +		dev_err(&pdev->dev, "driver data not available\n"); +		return -EINVAL; +	} + +	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); +	if (!drvdata) { +		dev_err(dev, "failed to allocate memory for driver's " +				"private data\n"); +		return -ENOMEM; +	} +	drvdata->ctrl = ctrl; +	drvdata->dev = dev; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		dev_err(dev, "cannot find IO resource\n"); +		return -ENOENT; +	} + +	drvdata->virt_base = devm_request_and_ioremap(&pdev->dev, res); +	if (!drvdata->virt_base) { +		dev_err(dev, "ioremap failed\n"); +		return -ENODEV; +	} + +	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +	if (res) +		drvdata->irq = res->start; + +	ret = samsung_gpiolib_register(pdev, drvdata); +	if (ret) +		return ret; + +	ret = samsung_pinctrl_register(pdev, drvdata); +	if (ret) { +		samsung_gpiolib_unregister(pdev, drvdata); +		return ret; +	} + +	if (ctrl->eint_gpio_init) +		ctrl->eint_gpio_init(drvdata); +	if (ctrl->eint_wkup_init) +		ctrl->eint_wkup_init(drvdata); + +	platform_set_drvdata(pdev, drvdata); +	return 0; +} + +static const struct of_device_id samsung_pinctrl_dt_match[] = { +	{ .compatible = "samsung,pinctrl-exynos4210", +		.data = (void *)exynos4210_pin_ctrl }, +	{}, +}; +MODULE_DEVICE_TABLE(of, samsung_pinctrl_dt_match); + +static struct platform_driver samsung_pinctrl_driver = { +	.probe		= samsung_pinctrl_probe, +	.driver = { +		.name	= "samsung-pinctrl", +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(samsung_pinctrl_dt_match), +	}, +}; + +static int __init samsung_pinctrl_drv_register(void) +{ +	return platform_driver_register(&samsung_pinctrl_driver); +} +postcore_initcall(samsung_pinctrl_drv_register); + +static void __exit samsung_pinctrl_drv_unregister(void) +{ +	platform_driver_unregister(&samsung_pinctrl_driver); +} +module_exit(samsung_pinctrl_drv_unregister); + +MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com>"); +MODULE_DESCRIPTION("Samsung pinctrl driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pinctrl/pinctrl-samsung.h b/drivers/pinctrl/pinctrl-samsung.h new file mode 100644 index 00000000000..b8956934cda --- /dev/null +++ b/drivers/pinctrl/pinctrl-samsung.h @@ -0,0 +1,239 @@ +/* + * pin-controller/pin-mux/pin-config/gpio-driver for Samsung's SoC's. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + *		http://www.samsung.com + * Copyright (c) 2012 Linaro Ltd + *		http://www.linaro.org + * + * Author: Thomas Abraham <thomas.ab@samsung.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; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __PINCTRL_SAMSUNG_H +#define __PINCTRL_SAMSUNG_H + +#include <linux/pinctrl/pinctrl.h> +#include <linux/pinctrl/pinmux.h> +#include <linux/pinctrl/pinconf.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pinctrl/machine.h> + +/* register offsets within a pin bank */ +#define DAT_REG		0x4 +#define PUD_REG		0x8 +#define DRV_REG		0xC +#define CONPDN_REG	0x10 +#define PUDPDN_REG	0x14 + +/* pinmux function number for pin as gpio output line */ +#define FUNC_OUTPUT	0x1 + +/** + * enum pincfg_type - possible pin configuration types supported. + * @PINCFG_TYPE_PUD: Pull up/down configuration. + * @PINCFG_TYPE_DRV: Drive strength configuration. + * @PINCFG_TYPE_CON_PDN: Pin function in power down mode. + * @PINCFG_TYPE_PUD_PDN: Pull up/down configuration in power down mode. + */ +enum pincfg_type { +	PINCFG_TYPE_PUD, +	PINCFG_TYPE_DRV, +	PINCFG_TYPE_CON_PDN, +	PINCFG_TYPE_PUD_PDN, +}; + +/* + * pin configuration (pull up/down and drive strength) type and its value are + * packed together into a 16-bits. The upper 8-bits represent the configuration + * type and the lower 8-bits hold the value of the configuration type. + */ +#define PINCFG_TYPE_MASK		0xFF +#define PINCFG_VALUE_SHIFT		8 +#define PINCFG_VALUE_MASK		(0xFF << PINCFG_VALUE_SHIFT) +#define PINCFG_PACK(type, value)	(((value) << PINCFG_VALUE_SHIFT) | type) +#define PINCFG_UNPACK_TYPE(cfg)		((cfg) & PINCFG_TYPE_MASK) +#define PINCFG_UNPACK_VALUE(cfg)	(((cfg) & PINCFG_VALUE_MASK) >> \ +						PINCFG_VALUE_SHIFT) +/** + * enum eint_type - possible external interrupt types. + * @EINT_TYPE_NONE: bank does not support external interrupts + * @EINT_TYPE_GPIO: bank supportes external gpio interrupts + * @EINT_TYPE_WKUP: bank supportes external wakeup interrupts + * + * Samsung GPIO controller groups all the available pins into banks. The pins + * in a pin bank can support external gpio interrupts or external wakeup + * interrupts or no interrupts at all. From a software perspective, the only + * difference between external gpio and external wakeup interrupts is that + * the wakeup interrupts can additionally wakeup the system if it is in + * suspended state. + */ +enum eint_type { +	EINT_TYPE_NONE, +	EINT_TYPE_GPIO, +	EINT_TYPE_WKUP, +}; + +/* maximum length of a pin in pin descriptor (example: "gpa0-0") */ +#define PIN_NAME_LENGTH	10 + +#define PIN_GROUP(n, p, f)				\ +	{						\ +		.name		= n,			\ +		.pins		= p,			\ +		.num_pins	= ARRAY_SIZE(p),	\ +		.func		= f			\ +	} + +#define PMX_FUNC(n, g)					\ +	{						\ +		.name		= n,			\ +		.groups		= g,			\ +		.num_groups	= ARRAY_SIZE(g),	\ +	} + +struct samsung_pinctrl_drv_data; + +/** + * struct samsung_pin_bank: represent a controller pin-bank. + * @reg_offset: starting offset of the pin-bank registers. + * @pin_base: starting pin number of the bank. + * @nr_pins: number of pins included in this bank. + * @func_width: width of the function selector bit field. + * @pud_width: width of the pin pull up/down selector bit field. + * @drv_width: width of the pin driver strength selector bit field. + * @conpdn_width: width of the sleep mode function selector bin field. + * @pudpdn_width: width of the sleep mode pull up/down selector bit field. + * @eint_type: type of the external interrupt supported by the bank. + * @irq_base: starting controller local irq number of the bank. + * @name: name to be prefixed for each pin in this pin bank. + */ +struct samsung_pin_bank { +	u32		pctl_offset; +	u32		pin_base; +	u8		nr_pins; +	u8		func_width; +	u8		pud_width; +	u8		drv_width; +	u8		conpdn_width; +	u8		pudpdn_width; +	enum eint_type	eint_type; +	u32		irq_base; +	char		*name; +}; + +/** + * struct samsung_pin_ctrl: represent a pin controller. + * @pin_banks: list of pin banks included in this controller. + * @nr_banks: number of pin banks. + * @base: starting system wide pin number. + * @nr_pins: number of pins supported by the controller. + * @nr_gint: number of external gpio interrupts supported. + * @nr_wint: number of external wakeup interrupts supported. + * @geint_con: offset of the ext-gpio controller registers. + * @geint_mask: offset of the ext-gpio interrupt mask registers. + * @geint_pend: offset of the ext-gpio interrupt pending registers. + * @weint_con: offset of the ext-wakeup controller registers. + * @weint_mask: offset of the ext-wakeup interrupt mask registers. + * @weint_pend: offset of the ext-wakeup interrupt pending registers. + * @svc: offset of the interrupt service register. + * @eint_gpio_init: platform specific callback to setup the external gpio + *	interrupts for the controller. + * @eint_wkup_init: platform specific callback to setup the external wakeup + *	interrupts for the controller. + * @label: for debug information. + */ +struct samsung_pin_ctrl { +	struct samsung_pin_bank	*pin_banks; +	u32		nr_banks; + +	u32		base; +	u32		nr_pins; +	u32		nr_gint; +	u32		nr_wint; + +	u32		geint_con; +	u32		geint_mask; +	u32		geint_pend; + +	u32		weint_con; +	u32		weint_mask; +	u32		weint_pend; + +	u32		svc; + +	int		(*eint_gpio_init)(struct samsung_pinctrl_drv_data *); +	int		(*eint_wkup_init)(struct samsung_pinctrl_drv_data *); +	char		*label; +}; + +/** + * struct samsung_pinctrl_drv_data: wrapper for holding driver data together. + * @virt_base: register base address of the controller. + * @dev: device instance representing the controller. + * @irq: interrpt number used by the controller to notify gpio interrupts. + * @ctrl: pin controller instance managed by the driver. + * @pctl: pin controller descriptor registered with the pinctrl subsystem. + * @pctl_dev: cookie representing pinctrl device instance. + * @pin_groups: list of pin groups available to the driver. + * @nr_groups: number of such pin groups. + * @pmx_functions: list of pin functions available to the driver. + * @nr_function: number of such pin functions. + * @gc: gpio_chip instance registered with gpiolib. + * @grange: linux gpio pin range supported by this controller. + */ +struct samsung_pinctrl_drv_data { +	void __iomem			*virt_base; +	struct device			*dev; +	int				irq; + +	struct samsung_pin_ctrl		*ctrl; +	struct pinctrl_desc		pctl; +	struct pinctrl_dev		*pctl_dev; + +	const struct samsung_pin_group	*pin_groups; +	unsigned int			nr_groups; +	const struct samsung_pmx_func	*pmx_functions; +	unsigned int			nr_functions; + +	struct irq_domain		*gpio_irqd; +	struct irq_domain		*wkup_irqd; + +	struct gpio_chip		*gc; +	struct pinctrl_gpio_range	grange; +}; + +/** + * struct samsung_pin_group: represent group of pins of a pinmux function. + * @name: name of the pin group, used to lookup the group. + * @pins: the pins included in this group. + * @num_pins: number of pins included in this group. + * @func: the function number to be programmed when selected. + */ +struct samsung_pin_group { +	const char		*name; +	const unsigned int	*pins; +	u8			num_pins; +	u8			func; +}; + +/** + * struct samsung_pmx_func: represent a pin function. + * @name: name of the pin function, used to lookup the function. + * @groups: one or more names of pin groups that provide this function. + * @num_groups: number of groups included in @groups. + */ +struct samsung_pmx_func { +	const char		*name; +	const char		**groups; +	u8			num_groups; +}; + +/* list of all exported SoC specific data */ +extern struct samsung_pin_ctrl exynos4210_pin_ctrl[]; + +#endif /* __PINCTRL_SAMSUNG_H */  |