diff options
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/Kconfig | 23 | ||||
-rw-r--r-- | drivers/char/Makefile | 6 | ||||
-rw-r--r-- | drivers/char/hw_random/Kconfig | 24 | ||||
-rw-r--r-- | drivers/char/hw_random/Makefile | 2 | ||||
-rw-r--r-- | drivers/char/hw_random/fsl-rnga.c | 238 | ||||
-rw-r--r-- | drivers/char/hw_random/fsl-rngc.c | 372 | ||||
-rw-r--r-- | drivers/char/imx_sim.c | 1497 | ||||
-rw-r--r-- | drivers/char/mxc_iim.c | 161 | ||||
-rw-r--r-- | drivers/char/mxc_si4702.c | 1221 | ||||
-rw-r--r-- | drivers/char/mxs_viim.c | 175 |
10 files changed, 3718 insertions, 1 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 6a06913b01d3..37492dcf15c7 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -431,6 +431,29 @@ config SGI_MBCS If you have an SGI Altix with an attached SABrick say Y or M here, otherwise say N. +config FM_SI4702 + tristate "SI4702 FM device driver" + depends on (MACH_MX31_3DS || MACH_MX35_3DS || MACH_MX37_3DS || MACH_MX51_3DS) + default n + +config MXC_IIM + tristate "MXC IIM device driver" + depends on ARCH_MXC + help + Support for access to MXC IIM device, most people should say N here. + +config MXS_VIIM + tristate "MXS Virtual IIM device driver" + depends on (ARCH_STMP3XXX || ARCH_MXS || ARCH_MX5) + help + Support for access to MXS Virtual IIM device, most people should say N here. + +config IMX_SIM + tristate "IMX SIM support" + depends on (ARCH_MX5 || ARCH_MX25) + ---help--- + Say Y to enable the SIM driver support. + source "drivers/serial/Kconfig" config UNIX98_PTYS diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 66f779ad4f4c..c711f02de8f7 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -9,6 +9,11 @@ FONTMAPFILE = cp437.uni obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o tty_buffer.o tty_port.o +obj-$(CONFIG_FM_SI4702) += mxc_si4702.o +obj-$(CONFIG_MXC_IIM) += mxc_iim.o +obj-$(CONFIG_MXS_VIIM) += mxs_viim.o +obj-$(CONFIG_IMX_SIM) += imx_sim.o + obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o obj-y += misc.o @@ -97,7 +102,6 @@ obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_CS5535_GPIO) += cs5535_gpio.o obj-$(CONFIG_GPIO_TB0219) += tb0219.o obj-$(CONFIG_TELCLOCK) += tlclk.o - obj-$(CONFIG_MWAVE) += mwave/ obj-$(CONFIG_AGP) += agp/ obj-$(CONFIG_PCMCIA) += pcmcia/ diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index ce66a70184f7..108a752c9132 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -60,6 +60,30 @@ config HW_RANDOM_AMD If unsure, say Y. +config HW_RANDOM_FSL_RNGA + tristate "Freescale RNGA Random Number Generator" + depends on HW_RANDOM && ARCH_HAS_RNGA && !MXC_SECURITY_RNG + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Freescale i.MX processors. + + To compile this driver as a module, choose M here: the + module will be called fsl-rnga. + + If unsure, say Y. + +config HW_RANDOM_FSL_RNGC + tristate "Freescale RNGC Random Number Generator" + depends on HW_RANDOM && ARCH_HAS_RNGC && !MXC_SECURITY_RNG + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Freescale i.MX processors. + + To compile this driver as a module, choose M here: the + module will be called fsl-rngc. + + If unsure, say Y. + config HW_RANDOM_GEODE tristate "AMD Geode HW Random Number Generator support" depends on HW_RANDOM && X86_32 && PCI diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index 676828ba8123..c6170069f998 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -7,6 +7,8 @@ rng-core-y := core.o obj-$(CONFIG_HW_RANDOM_TIMERIOMEM) += timeriomem-rng.o obj-$(CONFIG_HW_RANDOM_INTEL) += intel-rng.o obj-$(CONFIG_HW_RANDOM_AMD) += amd-rng.o +obj-$(CONFIG_HW_RANDOM_FSL_RNGA) += fsl-rnga.o +obj-$(CONFIG_HW_RANDOM_FSL_RNGC) += fsl-rngc.o obj-$(CONFIG_HW_RANDOM_GEODE) += geode-rng.o obj-$(CONFIG_HW_RANDOM_N2RNG) += n2-rng.o n2-rng-y := n2-drv.o n2-asm.o diff --git a/drivers/char/hw_random/fsl-rnga.c b/drivers/char/hw_random/fsl-rnga.c new file mode 100644 index 000000000000..a5c8065604d4 --- /dev/null +++ b/drivers/char/hw_random/fsl-rnga.c @@ -0,0 +1,238 @@ +/* + * RNG driver for Freescale RNGA + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) + * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> + * + * derived from + * + * Hardware driver for the AMD 768 Random Number Generator (RNG) + * (c) Copyright 2001 Red Hat Inc <alan@redhat.com> + * + * derived from + * + * Hardware driver for Intel i810 Random Number Generator (RNG) + * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com> + * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/hw_random.h> +#include <linux/io.h> + +/* RNGA Registers */ +#define RNGA_CONTROL 0x00 +#define RNGA_STATUS 0x04 +#define RNGA_ENTROPY 0x08 +#define RNGA_OUTPUT_FIFO 0x0c +#define RNGA_MODE 0x10 +#define RNGA_VERIFICATION_CONTROL 0x14 +#define RNGA_OSC_CONTROL_COUNTER 0x18 +#define RNGA_OSC1_COUNTER 0x1c +#define RNGA_OSC2_COUNTER 0x20 +#define RNGA_OSC_COUNTER_STATUS 0x24 + +/* RNGA Registers Range */ +#define RNG_ADDR_RANGE 0x28 + +/* RNGA Control Register */ +#define RNGA_CONTROL_SLEEP 0x00000010 +#define RNGA_CONTROL_CLEAR_INT 0x00000008 +#define RNGA_CONTROL_MASK_INTS 0x00000004 +#define RNGA_CONTROL_HIGH_ASSURANCE 0x00000002 +#define RNGA_CONTROL_GO 0x00000001 + +#define RNGA_STATUS_LEVEL_MASK 0x0000ff00 + +/* RNGA Status Register */ +#define RNGA_STATUS_OSC_DEAD 0x80000000 +#define RNGA_STATUS_SLEEP 0x00000010 +#define RNGA_STATUS_ERROR_INT 0x00000008 +#define RNGA_STATUS_FIFO_UNDERFLOW 0x00000004 +#define RNGA_STATUS_LAST_READ_STATUS 0x00000002 +#define RNGA_STATUS_SECURITY_VIOLATION 0x00000001 + +static struct platform_device *rng_dev; + +static int fsl_rnga_data_present(struct hwrng *rng) +{ + int level; + u32 rng_base = (u32) rng->priv; + + /* how many random numbers is in FIFO? [0-16] */ + level = ((__raw_readl(rng_base + RNGA_STATUS) & + RNGA_STATUS_LEVEL_MASK) >> 8); + + return level > 0 ? 1 : 0; +} + +static int fsl_rnga_data_read(struct hwrng *rng, u32 * data) +{ + int err; + u32 ctrl, rng_base = (u32) rng->priv; + + /* retrieve a random number from FIFO */ + *data = __raw_readl(rng_base + RNGA_OUTPUT_FIFO); + + /* some error while reading this random number? */ + err = __raw_readl(rng_base + RNGA_STATUS) & RNGA_STATUS_ERROR_INT; + + /* if error: clear error interrupt, but doesn't return random number */ + if (err) { + dev_dbg(&rng_dev->dev, "Error while reading random number!\n"); + ctrl = __raw_readl(rng_base + RNGA_CONTROL); + __raw_writel(ctrl | RNGA_CONTROL_CLEAR_INT, + rng_base + RNGA_CONTROL); + return 0; + } else + return 4; +} + +static int fsl_rnga_init(struct hwrng *rng) +{ + u32 ctrl, osc, rng_base = (u32) rng->priv; + + /* wake up */ + ctrl = __raw_readl(rng_base + RNGA_CONTROL); + __raw_writel(ctrl & ~RNGA_CONTROL_SLEEP, rng_base + RNGA_CONTROL); + + /* verify if oscillator is working */ + osc = __raw_readl(rng_base + RNGA_STATUS); + if (osc & RNGA_STATUS_OSC_DEAD) { + dev_err(&rng_dev->dev, "RNGA Oscillator is dead!\n"); + return -ENODEV; + } + + /* go running */ + ctrl = __raw_readl(rng_base + RNGA_CONTROL); + __raw_writel(ctrl | RNGA_CONTROL_GO, rng_base + RNGA_CONTROL); + + return 0; +} + +static void fsl_rnga_cleanup(struct hwrng *rng) +{ + u32 ctrl, rng_base = (u32) rng->priv; + + ctrl = __raw_readl(rng_base + RNGA_CONTROL); + + /* stop rnga */ + __raw_writel(ctrl & ~RNGA_CONTROL_GO, rng_base + RNGA_CONTROL); +} + +static struct hwrng fsl_rnga = { + .name = "fsl-rnga", + .init = fsl_rnga_init, + .cleanup = fsl_rnga_cleanup, + .data_present = fsl_rnga_data_present, + .data_read = fsl_rnga_data_read +}; + +static int __init fsl_rnga_probe(struct platform_device *pdev) +{ + int err = -ENODEV; + struct clk *clk; + struct resource *res, *mem; + void __iomem *rng_base = NULL; + + if (rng_dev) + return -EBUSY; + + clk = clk_get(NULL, "rng_clk"); + + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Could not get rng_clk!\n"); + err = PTR_ERR(clk); + return err; + } + + clk_enable(clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) + return -ENOENT; + + mem = request_mem_region(res->start, res->end - res->start, pdev->name); + + if (mem == NULL) + return -EBUSY; + + dev_set_drvdata(&pdev->dev, mem); + rng_base = ioremap(res->start, res->end - res->start); + + fsl_rnga.priv = (unsigned long)rng_base; + + err = hwrng_register(&fsl_rnga); + if (err) { + dev_err(&pdev->dev, "FSL RNGA registering failed (%d)\n", err); + return err; + } + + rng_dev = pdev; + + dev_info(&pdev->dev, "FSL RNGA Registered.\n"); + + return 0; +} + +static int __exit fsl_rnga_remove(struct platform_device *pdev) +{ + struct resource *mem = dev_get_drvdata(&pdev->dev); + void __iomem *rng_base = (void __iomem *)fsl_rnga.priv; + + hwrng_unregister(&fsl_rnga); + + release_resource(mem); + + iounmap(rng_base); + + return 0; +} + +static struct platform_driver fsl_rnga_driver = { + .driver = { + .name = "fsl_rnga", + .owner = THIS_MODULE, + }, + .remove = __exit_p(fsl_rnga_remove), +}; + +static int __init mod_init(void) +{ + return platform_driver_probe(&fsl_rnga_driver, fsl_rnga_probe); +} + +static void __exit mod_exit(void) +{ + platform_driver_unregister(&fsl_rnga_driver); +} + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("H/W RNGA driver for i.MX"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/fsl-rngc.c b/drivers/char/hw_random/fsl-rngc.c new file mode 100644 index 000000000000..9bf78e846fa0 --- /dev/null +++ b/drivers/char/hw_random/fsl-rngc.c @@ -0,0 +1,372 @@ +/* + * RNG driver for Freescale RNGC + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) + * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> + * + * derived from + * + * Hardware driver for the AMD 768 Random Number Generator (RNG) + * (c) Copyright 2001 Red Hat Inc <alan@redhat.com> + * + * derived from + * + * Hardware driver for Intel i810 Random Number Generator (RNG) + * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com> + * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <asm/hardware.h> + +#define RNGC_VERSION_MAJOR3 3 + +#define RNGC_VERSION_ID 0x0000 +#define RNGC_COMMAND 0x0004 +#define RNGC_CONTROL 0x0008 +#define RNGC_STATUS 0x000C +#define RNGC_ERROR 0x0010 +#define RNGC_FIFO 0x0014 +#define RNGC_VERIF_CTRL 0x0020 +#define RNGC_OSC_CTRL_COUNT 0x0028 +#define RNGC_OSC_COUNT 0x002C +#define RNGC_OSC_COUNT_STATUS 0x0030 + +#define RNGC_VERID_ZEROS_MASK 0x0f000000 +#define RNGC_VERID_RNG_TYPE_MASK 0xf0000000 +#define RNGC_VERID_RNG_TYPE_SHIFT 28 +#define RNGC_VERID_CHIP_VERSION_MASK 0x00ff0000 +#define RNGC_VERID_CHIP_VERSION_SHIFT 16 +#define RNGC_VERID_VERSION_MAJOR_MASK 0x0000ff00 +#define RNGC_VERID_VERSION_MAJOR_SHIFT 8 +#define RNGC_VERID_VERSION_MINOR_MASK 0x000000ff +#define RNGC_VERID_VERSION_MINOR_SHIFT 0 + +#define RNGC_CMD_ZEROS_MASK 0xffffff8c +#define RNGC_CMD_SW_RST 0x00000040 +#define RNGC_CMD_CLR_ERR 0x00000020 +#define RNGC_CMD_CLR_INT 0x00000010 +#define RNGC_CMD_SEED 0x00000002 +#define RNGC_CMD_SELF_TEST 0x00000001 + +#define RNGC_CTRL_ZEROS_MASK 0xfffffc8c +#define RNGC_CTRL_CTL_ACC 0x00000200 +#define RNGC_CTRL_VERIF_MODE 0x00000100 +#define RNGC_CTRL_MASK_ERROR 0x00000040 + +#define RNGC_CTRL_MASK_DONE 0x00000020 +#define RNGC_CTRL_AUTO_SEED 0x00000010 +#define RNGC_CTRL_FIFO_UFLOW_MASK 0x00000003 +#define RNGC_CTRL_FIFO_UFLOW_SHIFT 0 + +#define RNGC_CTRL_FIFO_UFLOW_ZEROS_ERROR 0 +#define RNGC_CTRL_FIFO_UFLOW_ZEROS_ERROR2 1 +#define RNGC_CTRL_FIFO_UFLOW_BUS_XFR 2 +#define RNGC_CTRL_FIFO_UFLOW_ZEROS_INTR 3 + +#define RNGC_STATUS_ST_PF_MASK 0x00c00000 +#define RNGC_STATUS_ST_PF_SHIFT 22 +#define RNGC_STATUS_ST_PF_TRNG 0x00800000 +#define RNGC_STATUS_ST_PF_PRNG 0x00400000 +#define RNGC_STATUS_ERROR 0x00010000 +#define RNGC_STATUS_FIFO_SIZE_MASK 0x0000f000 +#define RNGC_STATUS_FIFO_SIZE_SHIFT 12 +#define RNGC_STATUS_FIFO_LEVEL_MASK 0x00000f00 +#define RNGC_STATUS_FIFO_LEVEL_SHIFT 8 +#define RNGC_STATUS_NEXT_SEED_DONE 0x00000040 +#define RNGC_STATUS_SEED_DONE 0x00000020 +#define RNGC_STATUS_ST_DONE 0x00000010 +#define RNGC_STATUS_RESEED 0x00000008 +#define RNGC_STATUS_SLEEP 0x00000004 +#define RNGC_STATUS_BUSY 0x00000002 +#define RNGC_STATUS_SEC_STATE 0x00000001 + +#define RNGC_ERROR_STATUS_ZEROS_MASK 0xffffffc0 +#define RNGC_ERROR_STATUS_BAD_KEY 0x00000040 +#define RNGC_ERROR_STATUS_RAND_ERR 0x00000020 +#define RNGC_ERROR_STATUS_FIFO_ERR 0x00000010 +#define RNGC_ERROR_STATUS_STAT_ERR 0x00000008 +#define RNGC_ERROR_STATUS_ST_ERR 0x00000004 +#define RNGC_ERROR_STATUS_OSC_ERR 0x00000002 +#define RNGC_ERROR_STATUS_LFSR_ERR 0x00000001 + +#define RNG_ADDR_RANGE 0x34 + +static DECLARE_COMPLETION(rng_self_testing); +static DECLARE_COMPLETION(rng_seed_done); + +static struct platform_device *rng_dev; + +int irq_rng; + +static int fsl_rngc_data_present(struct hwrng *rng) +{ + int level; + u32 rngc_base = (u32) rng->priv; + + /* how many random numbers are in FIFO? [0-16] */ + level = (__raw_readl(rngc_base + RNGC_STATUS) & + RNGC_STATUS_FIFO_LEVEL_MASK) >> RNGC_STATUS_FIFO_LEVEL_SHIFT; + + return level > 0 ? 1 : 0; +} + +static int fsl_rngc_data_read(struct hwrng *rng, u32 * data) +{ + int err; + u32 rngc_base = (u32) rng->priv; + + /* retrieve a random number from FIFO */ + *data = __raw_readl(rngc_base + RNGC_FIFO); + + /* is there some error while reading this random number? */ + err = __raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_ERROR; + + /* if error happened doesn't return random number */ + return err ? 0 : 4; +} + +static irqreturn_t rngc_irq(int irq, void *dev) +{ + int handled = 0; + u32 rngc_base = (u32) dev; + + /* is the seed creation done? */ + if (__raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_SEED_DONE) { + complete(&rng_seed_done); + __raw_writel(RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR, + rngc_base + RNGC_COMMAND); + handled = 1; + } + + /* is the self test done? */ + if (__raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_ST_DONE) { + complete(&rng_self_testing); + __raw_writel(RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR, + rngc_base + RNGC_COMMAND); + handled = 1; + } + + /* is there any error? */ + if (__raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_ERROR) { + /* clear interrupt */ + __raw_writel(RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR, + rngc_base + RNGC_COMMAND); + handled = 1; + } + + return handled; +} + +static int fsl_rngc_init(struct hwrng *rng) +{ + int err; + u32 cmd, ctrl, osc, rngc_base = (u32) rng->priv; + + INIT_COMPLETION(rng_self_testing); + INIT_COMPLETION(rng_seed_done); + + err = __raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_ERROR; + if (err) { + /* is this a bad keys error ? */ + if (__raw_readl(rngc_base + RNGC_ERROR) & + RNGC_ERROR_STATUS_BAD_KEY) { + dev_err(&rng_dev->dev, "Can't start, Bad Keys.\n"); + return -EIO; + } + } + + /* mask all interrupts, will be unmasked soon */ + ctrl = __raw_readl(rngc_base + RNGC_CONTROL); + __raw_writel(ctrl | RNGC_CTRL_MASK_DONE | RNGC_CTRL_MASK_ERROR, + rngc_base + RNGC_CONTROL); + + /* verify if oscillator is working */ + osc = __raw_readl(rngc_base + RNGC_ERROR); + if (osc & RNGC_ERROR_STATUS_OSC_ERR) { + dev_err(&rng_dev->dev, "RNGC Oscillator is dead!\n"); + return -EIO; + } + + err = request_irq(irq_rng, rngc_irq, 0, "fsl_rngc", (void *)rng->priv); + if (err) { + dev_err(&rng_dev->dev, "Can't get interrupt working.\n"); + return -EIO; + } + + /* do self test, repeat until get success */ + do { + /* clear error */ + cmd = __raw_readl(rngc_base + RNGC_COMMAND); + __raw_writel(cmd | RNGC_CMD_CLR_ERR, rngc_base + RNGC_COMMAND); + + /* unmask all interrupt */ + ctrl = __raw_readl(rngc_base + RNGC_CONTROL); + __raw_writel(ctrl & ~(RNGC_CTRL_MASK_DONE | + RNGC_CTRL_MASK_ERROR), rngc_base + RNGC_CONTROL); + + /* run self test */ + cmd = __raw_readl(rngc_base + RNGC_COMMAND); + __raw_writel(cmd | RNGC_CMD_SELF_TEST, + rngc_base + RNGC_COMMAND); + + wait_for_completion(&rng_self_testing); + + } while (__raw_readl(rngc_base + RNGC_ERROR) & + RNGC_ERROR_STATUS_ST_ERR); + + /* clear interrupt. Is it really necessary here? */ + __raw_writel(RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR, + rngc_base + RNGC_COMMAND); + + /* create seed, repeat while there is some statistical error */ + do { + /* clear error */ + cmd = __raw_readl(rngc_base + RNGC_COMMAND); + __raw_writel(cmd | RNGC_CMD_CLR_ERR, rngc_base + RNGC_COMMAND); + + /* seed creation */ + cmd = __raw_readl(rngc_base + RNGC_COMMAND); + __raw_writel(cmd | RNGC_CMD_SEED, rngc_base + RNGC_COMMAND); + + wait_for_completion(&rng_seed_done); + + } while (__raw_readl(rngc_base + RNGC_ERROR) & + RNGC_ERROR_STATUS_STAT_ERR); + + err = __raw_readl(rngc_base + RNGC_ERROR) & + (RNGC_ERROR_STATUS_STAT_ERR | + RNGC_ERROR_STATUS_RAND_ERR | + RNGC_ERROR_STATUS_FIFO_ERR | + RNGC_ERROR_STATUS_ST_ERR | + RNGC_ERROR_STATUS_OSC_ERR | + RNGC_ERROR_STATUS_LFSR_ERR); + + if (err) { + dev_err(&rng_dev->dev, "FSL RNGC appears inoperable.\n"); + return -EIO; + } + + return 0; +} + +static struct hwrng fsl_rngc = { + .name = "fsl-rngc", + .init = fsl_rngc_init, + .data_present = fsl_rngc_data_present, + .data_read = fsl_rngc_data_read +}; + +static int __init fsl_rngc_probe(struct platform_device *pdev) +{ + int err = -ENODEV; + struct clk *clk; + struct resource *res, *mem; + void __iomem *rngc_base = NULL; + + if (rng_dev) + return -EBUSY; + + clk = clk_get(NULL, "rng_clk"); + + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Can not get rng_clk\n"); + err = PTR_ERR(clk); + return err; + } + + clk_enable(clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) + return -ENOENT; + + mem = request_mem_region(res->start, res->end - res->start, pdev->name); + + if (mem == NULL) + return -EBUSY; + + dev_set_drvdata(&pdev->dev, mem); + rngc_base = ioremap(res->start, res->end - res->start); + + fsl_rngc.priv = (unsigned long)rngc_base; + + irq_rng = platform_get_irq(pdev, 0); + + err = hwrng_register(&fsl_rngc); + if (err) { + dev_err(&pdev->dev, "FSL RNGC registering failed (%d)\n", err); + return err; + } + + rng_dev = pdev; + + dev_info(&pdev->dev, "FSL RNGC Registered.\n"); + + return 0; +} + +static int __exit fsl_rngc_remove(struct platform_device *pdev) +{ + struct resource *mem = dev_get_drvdata(&pdev->dev); + void __iomem *rngc_base = (void __iomem *)fsl_rngc.priv; + + hwrng_unregister(&fsl_rngc); + + release_resource(mem); + + iounmap(rngc_base); + + return 0; +} + +static struct platform_driver fsl_rngc_driver = { + .driver = { + .name = "fsl_rngc", + .owner = THIS_MODULE, + }, + .remove = __exit_p(fsl_rngc_remove), +}; + +static int __init mod_init(void) +{ + return platform_driver_probe(&fsl_rngc_driver, fsl_rngc_probe); +} + +static void __exit mod_exit(void) +{ + platform_driver_unregister(&fsl_rngc_driver); +} + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("H/W RNGC driver for i.MX"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/imx_sim.c b/drivers/char/imx_sim.c new file mode 100644 index 000000000000..4d10f11e8012 --- /dev/null +++ b/drivers/char/imx_sim.c @@ -0,0 +1,1497 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_sim.c + * + * @brief Driver for Freescale IMX SIM interface + * + */ + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/delay.h> + +#include <linux/sched.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/clk.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/mxc_sim_interface.h> + +#include <asm/io.h> +#include <mach/hardware.h> + +#define SIM_INTERNAL_CLK 0 +#define SIM_RFU -1 + +/* Default communication parameters: FI=372, DI=1, PI1=5V, II=50mA, WWT=10 */ +#define SIM_PARAM_DEFAULT { 0, 1, 1, 5, 1, 0, 0, 0, 10 } + +/* Transmit and receive buffer sizes */ +#define SIM_XMT_BUFFER_SIZE 256 +#define SIM_RCV_BUFFER_SIZE 256 + +/* Interface character references */ +#define SIM_IFC_TXI(letter, number) (letter + number * 4) +#define SIM_IFC_TA1 SIM_IFC_TXI(0, 0) +#define SIM_IFC_TB1 SIM_IFC_TXI(0, 1) +#define SIM_IFC_TC1 SIM_IFC_TXI(0, 2) +#define SIM_IFC_TD1 SIM_IFC_TXI(0, 3) +#define SIM_IFC_TA2 SIM_IFC_TXI(1, 0) +#define SIM_IFC_TB2 SIM_IFC_TXI(1, 1) +#define SIM_IFC_TC2 SIM_IFC_TXI(1, 2) +#define SIM_IFC_TD2 SIM_IFC_TXI(1, 3) +#define SIM_IFC_TA3 SIM_IFC_TXI(2, 0) +#define SIM_IFC_TB3 SIM_IFC_TXI(2, 1) +#define SIM_IFC_TC3 SIM_IFC_TXI(2, 2) +#define SIM_IFC_TD3 SIM_IFC_TXI(2, 3) +#define SIM_IFC_TA4 SIM_IFC_TXI(3, 0) +#define SIM_IFC_TB4 SIM_IFC_TXI(3, 1) +#define SIM_IFC_TC4 SIM_IFC_TXI(3, 2) +#define SIM_IFC_TD4 SIM_IFC_TXI(3, 3) + +/* ATR and OPS states */ +#define SIM_STATE_REMOVED 0 +#define SIM_STATE_OPERATIONAL_IDLE 1 +#define SIM_STATE_OPERATIONAL_COMMAND 2 +#define SIM_STATE_OPERATIONAL_RESPONSE 3 +#define SIM_STATE_OPERATIONAL_STATUS1 4 +#define SIM_STATE_OPERATIONAL_STATUS2 5 +#define SIM_STATE_OPERATIONAL_PTS 6 +#define SIM_STATE_DETECTED_ATR_T0 7 +#define SIM_STATE_DETECTED_ATR_TS 8 +#define SIM_STATE_DETECTED_ATR_TXI 9 +#define SIM_STATE_DETECTED_ATR_THB 10 +#define SIM_STATE_DETECTED_ATR_TCK 11 + +/* Definitions of the offset of the SIM hardware registers */ +#define PORT1_CNTL 0x00 /* 00 */ +#define SETUP 0x04 /* 04 */ +#define PORT1_DETECT 0x08 /* 08 */ +#define PORT1_XMT_BUF 0x0C /* 0c */ +#define PORT1_RCV_BUF 0x10 /* 10 */ +#define PORT0_CNTL 0x14 /* 14 */ +#define CNTL 0x18 /* 18 */ +#define CLK_PRESCALER 0x1C /* 1c */ +#define RCV_THRESHOLD 0x20 /* 20 */ +#define ENABLE 0x24 /* 24 */ +#define XMT_STATUS 0x28 /* 28 */ +#define RCV_STATUS 0x2C /* 2c */ +#define INT_MASK 0x30 /* 30 */ +#define PORTO_XMT_BUF 0x34 /* 34 */ +#define PORT0_RCV_BUF 0x38 /* 38 */ +#define PORT0_DETECT 0x3C /* 3c */ +#define DATA_FORMAT 0x40 /* 40 */ +#define XMT_THRESHOLD 0x44 /* 44 */ +#define GUARD_CNTL 0x48 /* 48 */ +#define OD_CONFIG 0x4C /* 4c */ +#define RESET_CNTL 0x50 /* 50 */ +#define CHAR_WAIT 0x54 /* 54 */ +#define GPCNT 0x58 /* 58 */ +#define DIVISOR 0x5C /* 5c */ +#define BWT 0x60 /* 60 */ +#define BGT 0x64 /* 64 */ +#define BWT_H 0x68 /* 68 */ +#define XMT_FIFO_STAT 0x6C /* 6c */ +#define RCV_FIFO_CNT 0x70 /* 70 */ +#define RCV_FIFO_WPTR 0x74 /* 74 */ +#define RCV_FIFO_RPTR 0x78 /* 78 */ + +/* SIM port[0|1]_cntl register bits */ +#define SIM_PORT_CNTL_SFPD (1<<7) +#define SIM_PORT_CNTL_3VOLT (1<<6) +#define SIM_PORT_CNTL_SCSP (1<<5) +#define SIM_PORT_CNTL_SCEN (1<<4) +#define SIM_PORT_CNTL_SRST (1<<3) +#define SIM_PORT_CNTL_STEN (1<<2) +#define SIM_PORT_CNTL_SVEN (1<<1) +#define SIM_PORT_CNTL_SAPD (1<<0) + +/* SIM od_config register bits */ +#define SIM_OD_CONFIG_OD_P1 (1<<1) +#define SIM_OD_CONFIG_OD_P0 (1<<0) + +/* SIM enable register bits */ +#define SIM_ENABLE_XMTEN (1<<1) +#define SIM_ENABLE_RCVEN (1<<0) + +/* SIM int_mask register bits */ +#define SIM_INT_MASK_RFEM (1<<13) +#define SIM_INT_MASK_BGTM (1<<12) +#define SIM_INT_MASK_BWTM (1<<11) +#define SIM_INT_MASK_RTM (1<<10) +#define SIM_INT_MASK_CWTM (1<<9) +#define SIM_INT_MASK_GPCM (1<<8) +#define SIM_INT_MASK_TDTFM (1<<7) +#define SIM_INT_MASK_TFOM (1<<6) +#define SIM_INT_MASK_XTM (1<<5) +#define SIM_INT_MASK_TFEIM (1<<4) +#define SIM_INT_MASK_ETCIM (1<<3) +#define SIM_INT_MASK_OIM (1<<2) +#define SIM_INT_MASK_TCIM (1<<1) +#define SIM_INT_MASK_RIM (1<<0) + +/* SIM xmt_status register bits */ +#define SIM_XMT_STATUS_GPCNT (1<<8) +#define SIM_XMT_STATUS_TDTF (1<<7) +#define SIM_XMT_STATUS_TFO (1<<6) +#define SIM_XMT_STATUS_TC (1<<5) +#define SIM_XMT_STATUS_ETC (1<<4) +#define SIM_XMT_STATUS_TFE (1<<3) +#define SIM_XMT_STATUS_XTE (1<<0) + +/* SIM rcv_status register bits */ +#define SIM_RCV_STATUS_BGT (1<<11) +#define SIM_RCV_STATUS_BWT (1<<10) +#define SIM_RCV_STATUS_RTE (1<<9) +#define SIM_RCV_STATUS_CWT (1<<8) +#define SIM_RCV_STATUS_CRCOK (1<<7) +#define SIM_RCV_STATUS_LRCOK (1<<6) +#define SIM_RCV_STATUS_RDRF (1<<5) +#define SIM_RCV_STATUS_RFD (1<<4) +#define SIM_RCV_STATUS_RFE (1<<1) +#define SIM_RCV_STATUS_OEF (1<<0) + +/* SIM cntl register bits */ +#define SIM_CNTL_BWTEN (1<<15) +#define SIM_CNTL_XMT_CRC_LRC (1<<14) +#define SIM_CNTL_CRCEN (1<<13) +#define SIM_CNTL_LRCEN (1<<12) +#define SIM_CNTL_CWTEN (1<<11) +#define SIM_CNTL_SAMPLE12 (1<<4) +#define SIM_CNTL_ONACK (1<<3) +#define SIM_CNTL_ANACK (1<<2) +#define SIM_CNTL_ICM (1<<1) +#define SIM_CNTL_GPCNT_CLK_SEL(x) ((x&0x03)<<9) +#define SIM_CNTL_GPCNT_CLK_SEL_MASK (0x03<<9) +#define SIM_CNTL_BAUD_SEL(x) ((x&0x07)<<6) +#define SIM_CNTL_BAUD_SEL_MASK (0x07<<6) + +/* SIM rcv_threshold register bits */ +#define SIM_RCV_THRESHOLD_RTH(x) ((x&0x0f)<<9) +#define SIM_RCV_THRESHOLD_RTH_MASK (0x0f<<9) +#define SIM_RCV_THRESHOLD_RDT(x) ((x&0x1ff)<<0) +#define SIM_RCV_THRESHOLD_RDT_MASK (0x1ff<<0) + +/* SIM xmt_threshold register bits */ +#define SIM_XMT_THRESHOLD_XTH(x) ((x&0x0f)<<4) +#define SIM_XMT_THRESHOLD_XTH_MASK (0x0f<<4) +#define SIM_XMT_THRESHOLD_TDT(x) ((x&0x0f)<<0) +#define SIM_XMT_THRESHOLD_TDT_MASK (0x0f<<0) + +/* SIM guard_cntl register bits */ +#define SIM_GUARD_CNTL_RCVR11 (1<<8) +#define SIM_GIARD_CNTL_GETU(x) (x&0xff) +#define SIM_GIARD_CNTL_GETU_MASK (0xff) + +/* SIM port[0|]_detect register bits */ +#define SIM_PORT_DETECT_SPDS (1<<3) +#define SIM_PORT_DETECT_SPDP (1<<2) +#define SIM_PORT_DETECT_SDI (1<<1) +#define SIM_PORT_DETECT_SDIM (1<<0) + +/* END of REGS definitions */ + +/* ATR parser data (the parser state is stored in the main device structure) */ +typedef struct { + uint8_t T0; /* ATR T0 */ + uint8_t TS; /* ATR TS */ + /* ATR TA1, TB1, TC1, TD1, TB1, ... , TD4 */ + uint8_t TXI[16]; + uint8_t THB[15]; /* ATR historical bytes */ + uint8_t TCK; /* ATR checksum */ + uint16_t ifc_valid; /* valid interface characters */ + uint8_t ifc_current_valid; /* calid ifcs in the current batch */ + uint8_t cnt; /* number of current batch */ + uint8_t num_hb; /* number of historical bytes */ +} sim_atrparser_t; + +/* Main SIM driver structure */ +typedef struct { + /* card inserted = 1, ATR received = 2, card removed = 0 */ + int present; + /* current ATR or OPS state */ + int state; + /* current power state */ + int power; + /* error code occured during transfer */ + int errval; + struct clk *clk; /* Clock id */ + uint8_t clk_flag; + struct resource *res; /* IO map memory */ + void __iomem *ioaddr; /* Mapped address */ + int ipb_irq; /* sim ipb IRQ num */ + int dat_irq; /* sim dat IRQ num */ + /* parser for incoming ATR stream */ + sim_atrparser_t atrparser; + /* raw ATR stream received */ + sim_atr_t atr; + /* communication parameters according to ATR */ + sim_param_t param_atr; + /* current communication parameters */ + sim_param_t param; + /* current TPDU or PTS transfer */ + sim_xfer_t xfer; + /* transfer is on the way = 1, idle = 2 */ + int xfer_ongoing; + /* remaining bytes to transmit for the current transfer */ + int xmt_remaining; + /* transmit position */ + int xmt_pos; + /* receive position / number of bytes received */ + int rcv_count; + uint8_t rcv_buffer[SIM_RCV_BUFFER_SIZE]; + uint8_t xmt_buffer[SIM_XMT_BUFFER_SIZE]; + /* transfer completion notifier */ + struct completion xfer_done; + /* async notifier for card and ATR detection */ + struct fasync_struct *fasync; + /* Platform specific data */ + struct mxc_sim_platform_data *plat_data; +} sim_t; + +static int sim_param_F[] = { + SIM_INTERNAL_CLK, 372, 558, 744, 1116, 1488, 1860, SIM_RFU, + SIM_RFU, 512, 768, 1024, 1536, 2048, SIM_RFU, SIM_RFU +}; + +static int sim_param_D[] = { + SIM_RFU, 64 * 1, 64 * 2, 64 * 4, 64 * 8, 64 * 16, SIM_RFU, SIM_RFU, + SIM_RFU, SIM_RFU, 64 * 1 / 2, 64 * 1 / 4, 64 * 1 / 8, 64 * 1 / 16, + 64 * 1 / 32, 64 * 1 / 64 +}; + +static struct miscdevice sim_dev; + +/* Function: sim_calc_param + * + * Description: determine register values depending on communication parameters + * + * Parameters: + * uint32_t fi ATR frequency multiplier index + * uint32_t di ATR frequency divider index + * uint32_t* ptr_divisor location to store divisor result + * uint32_t* ptr_sample12 location to store sample12 result + * + * Return Values: + * SIM_OK calculation finished without errors + * -SIM_E_PARAM_DIVISOR_RANGE calculated divisor > 255 + * -SIM_E_PARAM_FBYD_NOTDIVBY8OR12 F/D not divisable by 12 (as required) + * -SIM_E_PARAM_FBYD_WITHFRACTION F/D has a remainder + * -SIM_E_PARAM_DI_INVALID frequency multiplyer index not supported + * -SIM_E_PARAM_FI_INVALID frequency divider index not supported + */ + +static int sim_calc_param(uint32_t fi, uint32_t di, uint32_t *ptr_divisor, + uint32_t *ptr_sample12) +{ + int32_t errval = SIM_OK; + int32_t f = sim_param_F[fi]; + int32_t d = sim_param_D[di]; + int32_t stage2_fra = (64 * f) % d; + int32_t stage2_div = (64 * f) / d; + uint32_t sample12 = 1; + uint32_t divisor = 31; + + pr_debug("%s entering.\n", __func__); + if ((f > 0) || (d > 0)) { + if (stage2_fra == 0) { + if ((stage2_div % 12) == 0) { + sample12 = 1; + divisor = stage2_div / 12; + } else if ((stage2_div % 8) == 0) { + sample12 = 0; + divisor = stage2_div / 8; + } else + sample12 = -1; + if (sample12 >= 0) { + if (divisor < 256) { + pr_debug("fi=%i", fi); + pr_debug("di=%i", di); + pr_debug("f=%i", f); + pr_debug("d=%i/64", d); + pr_debug("div=%i", stage2_div); + pr_debug("divisor=%i", divisor); + pr_debug("sample12=%i\n", sample12); + + *ptr_divisor = divisor; + *ptr_sample12 = sample12; + errval = SIM_OK; + } else + errval = -SIM_E_PARAM_DIVISOR_RANGE; + } else + errval = -SIM_E_PARAM_FBYD_NOTDIVBY8OR12; + } else + errval = -SIM_E_PARAM_FBYD_WITHFRACTION; + } else + errval = -SIM_E_PARAM_FI_INVALID; + + return errval; +}; + +/* Function: sim_set_param + * + * Description: apply communication parameters (setup devisor and sample12) + * + * Parameters: + * sim_t* sim pointer to SIM device handler + * sim_param_t* param pointer to communication parameters + * + * Return Values: + * see function sim_calc_param + */ + +static int sim_set_param(sim_t *sim, sim_param_t *param) +{ + uint32_t divisor, sample12, reg_data; + int errval; + + pr_debug("%s entering.\n", __func__); + errval = sim_calc_param(param->FI, param->DI, &divisor, &sample12); + if (errval == SIM_OK) { + __raw_writel(divisor, sim->ioaddr + DIVISOR); + if (sample12) { + reg_data = __raw_readl(sim->ioaddr + CNTL); + reg_data |= SIM_CNTL_SAMPLE12; + __raw_writel(reg_data, sim->ioaddr + CNTL); + } else { + reg_data = __raw_readl(sim->ioaddr + CNTL); + reg_data &= ~SIM_CNTL_SAMPLE12; + __raw_writel(reg_data, sim->ioaddr + CNTL); + } + } + + return errval; +}; + +/* Function: sim_atr_received + * + * Description: this function is called whenever a valid ATR has been received. + * It determines the communication parameters from the ATR received and notifies + * the user space application with SIGIO. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_atr_received(sim_t *sim) +{ + sim_param_t param_default = SIM_PARAM_DEFAULT; + sim->param_atr = param_default; + + pr_debug("%s entering.\n", __func__); + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TA1))) { + sim->param_atr.FI = sim->atrparser.TXI[SIM_IFC_TA1] >> 4; + sim->param_atr.DI = sim->atrparser.TXI[SIM_IFC_TA1] & 0x0f; + } + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TB1))) { + sim->param_atr.PI1 = (sim->atrparser.TXI[SIM_IFC_TB1] >> 4) + & 0x07; + sim->param_atr.II = sim->atrparser.TXI[SIM_IFC_TB1] & 0x07f; + } + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TC1))) + sim->param_atr.N = sim->atrparser.TXI[SIM_IFC_TC1]; + + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TD1))) + sim->param_atr.T = sim->atrparser.TXI[SIM_IFC_TD1] & 0x0f; + + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TB2))) + sim->param_atr.PI2 = sim->atrparser.TXI[SIM_IFC_TB2]; + + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TC2))) + sim->param_atr.WWT = sim->atrparser.TXI[SIM_IFC_TC2]; + + if (sim->fasync) + kill_fasync(&sim->fasync, SIGIO, POLL_IN); + +}; + +/* Function: sim_xmt_fill + * + * Description: fill the transmit FIFO until the FIFO is full or + * the end of the transmission has been reached. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_xmt_fill(sim_t *sim) +{ + uint32_t reg_data; + int bytesleft; + + reg_data = __raw_readl(sim->ioaddr + XMT_FIFO_STAT); + bytesleft = 16 - ((reg_data >> 8) & 0x0f); + + pr_debug("txfill: remaining=%i bytesleft=%i\n", + sim->xmt_remaining, bytesleft); + if (bytesleft > sim->xmt_remaining) + bytesleft = sim->xmt_remaining; + + sim->xmt_remaining -= bytesleft; + for (; bytesleft > 0; bytesleft--) { + __raw_writel(sim->xmt_buffer[sim->xmt_pos], + sim->ioaddr + PORT1_XMT_BUF); + sim->xmt_pos++; + }; +/* FIXME: optimization - keep filling until fifo full */ +}; + +/* Function: sim_xmt_start + * + * Description: initiate a transfer + * + * Parameters: + * sim_t* sim pointer to SIM device handler + * int pos position in the xfer transmit buffer + * int count number of bytes to be transmitted + */ + +static void sim_xmt_start(sim_t *sim, int pos, int count) +{ + uint32_t reg_data; + + pr_debug("tx\n"); + sim->xmt_remaining = count; + sim->xmt_pos = pos; + sim_xmt_fill(sim); + + if (sim->xmt_remaining) { + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data &= ~SIM_INT_MASK_TDTFM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + } else { + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data &= ~SIM_INT_MASK_TCIM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + __raw_writel(SIM_XMT_STATUS_TC | SIM_XMT_STATUS_TDTF, + sim->ioaddr + XMT_STATUS); + reg_data = __raw_readl(sim->ioaddr + ENABLE); + reg_data |= SIM_ENABLE_XMTEN; + __raw_writel(reg_data, sim->ioaddr + ENABLE); + } +}; + +/* Function: sim_atr_add + * + * Description: add a byte to the raw ATR string + * + * Parameters: + * sim_t* sim pointer to SIM device handler + * uint8_t data byte to be added + */ + +static void sim_atr_add(sim_t *sim, uint8_t data) +{ + pr_debug("%s entering.\n", __func__); + if (sim->atr.size < SIM_ATR_LENGTH_MAX) + sim->atr.t[sim->atr.size++] = data; + else + printk(KERN_ERR "sim.c: ATR received is too big!\n"); +}; + +/* Function: sim_fsm + * + * Description: main finite state machine running in ISR context. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + * uint8_t data byte received + */ + +static void sim_fsm(sim_t *sim, uint16_t data) +{ + uint32_t temp, i = 0; + switch (sim->state) { + + pr_debug("%s stat is %d \n", __func__, sim->state); + /* OPS FSM */ + + case SIM_STATE_OPERATIONAL_IDLE: + printk(KERN_INFO "data received unexpectidly (%04x)\n", data); + break; + + case SIM_STATE_OPERATIONAL_COMMAND: + if (data == sim->xmt_buffer[1]) { + if (sim->xfer.rcv_length) { + sim->state = SIM_STATE_OPERATIONAL_RESPONSE; + } else { + sim->state = SIM_STATE_OPERATIONAL_STATUS1; + if (sim->xfer.xmt_length > 5) + sim_xmt_start(sim, 5, + sim->xfer.xmt_length - 5); + }; + } else if (((data & 0xf0) == 0x60) | ((data & 0xf0) == 0x90)) { + sim->xfer.sw1 = data; + sim->state = SIM_STATE_OPERATIONAL_STATUS2; + } else { + sim->errval = -SIM_E_NACK; + complete(&sim->xfer_done); + }; + break; + + case SIM_STATE_OPERATIONAL_RESPONSE: + sim->rcv_buffer[sim->rcv_count] = data; + sim->rcv_count++; + if (sim->rcv_count == sim->xfer.rcv_length) + sim->state = SIM_STATE_OPERATIONAL_STATUS1; + break; + + case SIM_STATE_OPERATIONAL_STATUS1: + sim->xfer.sw1 = data; + sim->state = SIM_STATE_OPERATIONAL_STATUS2; + break; + + case SIM_STATE_OPERATIONAL_STATUS2: + sim->xfer.sw2 = data; + sim->state = SIM_STATE_OPERATIONAL_IDLE; + complete(&sim->xfer_done); + break; + + case SIM_STATE_OPERATIONAL_PTS: + sim->rcv_buffer[sim->rcv_count] = data; + sim->rcv_count++; + if (sim->rcv_count == sim->xfer.rcv_length) + sim->state = SIM_STATE_OPERATIONAL_IDLE; + break; + + /* ATR FSM */ + + case SIM_STATE_DETECTED_ATR_T0: + sim_atr_add(sim, data); + pr_debug("T0 %02x\n", data); + sim->atrparser.T0 = data; + sim->state = SIM_STATE_DETECTED_ATR_TS; + break; + + case SIM_STATE_DETECTED_ATR_TS: + sim_atr_add(sim, data); + pr_debug("TS %02x\n", data); + sim->atrparser.TS = data; + if (data & 0xf0) { + sim->atrparser.ifc_current_valid = (data >> 4) & 0x0f; + sim->atrparser.num_hb = data & 0x0f; + sim->atrparser.ifc_valid = 0; + sim->state = SIM_STATE_DETECTED_ATR_TXI; + sim->atrparser.cnt = 0; + } else { + goto sim_fsm_atr_thb; + }; + break; + + case SIM_STATE_DETECTED_ATR_TXI: + sim_atr_add(sim, data); + i = ffs(sim->atrparser.ifc_current_valid) - 1; + pr_debug("T%c%i %02x\n", 'A' + i, sim->atrparser.cnt + 1, data); + sim->atrparser.TXI[SIM_IFC_TXI(i, sim->atrparser.cnt)] = data; + sim->atrparser.ifc_valid |= 1 << SIM_IFC_TXI(i, + sim->atrparser. + cnt); + sim->atrparser.ifc_current_valid &= ~(1 << i); + + if (sim->atrparser.ifc_current_valid == 0) { + if (i == 3) { + sim->atrparser.ifc_current_valid = (data >> 4) + & 0x0f; + sim->atrparser.cnt++; + + if (sim->atrparser.cnt >= 4) { + /* error */ + printk(KERN_ERR "ERROR !\n"); + break; + }; + + if (sim->atrparser.ifc_current_valid == 0) + goto sim_fsm_atr_thb; + } else { +sim_fsm_atr_thb: + if (sim->atrparser.num_hb) { + sim->state = SIM_STATE_DETECTED_ATR_THB; + sim->atrparser.cnt = 0; + } else { + goto sim_fsm_atr_tck; + }; + }; + }; + break; + + case SIM_STATE_DETECTED_ATR_THB: + sim_atr_add(sim, data); + pr_debug("THB%i %02x\n", i, data); + sim->atrparser.THB[sim->atrparser.cnt] = data; + sim->atrparser.cnt++; + + if (sim->atrparser.cnt == sim->atrparser.num_hb) { +sim_fsm_atr_tck: + i = sim->atrparser.ifc_valid & (1 << (SIM_IFC_TD1)); + temp = sim->atrparser.TXI[SIM_IFC_TD1] & 0x0f; + if ((i && temp) == SIM_PROTOCOL_T1) + sim->state = SIM_STATE_DETECTED_ATR_TCK; + else + goto sim_fsm_atr_received; + }; + break; + + case SIM_STATE_DETECTED_ATR_TCK: + sim_atr_add(sim, data); + /* checksum not required for T=0 */ + sim->atrparser.TCK = data; +sim_fsm_atr_received: + sim->state = SIM_STATE_OPERATIONAL_IDLE; + sim->present = SIM_PRESENT_OPERATIONAL; + sim_atr_received(sim); + break; + }; +}; + +/* Function: sim_irq_handler + * + * Description: interrupt service routine. + * + * Parameters: + * int irq interrupt number + * void *dev_id pointer to SIM device handler + * + * Return values: + * IRQ_HANDLED OS specific + */ + +static irqreturn_t sim_irq_handler(int irq, void *dev_id) +{ + uint32_t reg_data, reg_data0, reg_data1; + + sim_t *sim = (sim_t *) dev_id; + + pr_debug("%s entering\n", __func__); + + reg_data0 = __raw_readl(sim->ioaddr + XMT_STATUS); + reg_data1 = __raw_readl(sim->ioaddr + INT_MASK); + if ((reg_data0 & SIM_XMT_STATUS_TC) + && (!(reg_data1 & SIM_INT_MASK_TCIM))) { + pr_debug("TC_IRQ\n"); + __raw_writel(SIM_XMT_STATUS_TC, sim->ioaddr + XMT_STATUS); + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data |= SIM_INT_MASK_TCIM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + reg_data = __raw_readl(sim->ioaddr + ENABLE); + reg_data &= ~SIM_ENABLE_XMTEN; + __raw_writel(reg_data, sim->ioaddr + ENABLE); + }; + + reg_data0 = __raw_readl(sim->ioaddr + XMT_STATUS); + reg_data1 = __raw_readl(sim->ioaddr + INT_MASK); + if ((reg_data0 & SIM_XMT_STATUS_TDTF) + && (!(reg_data1 & SIM_INT_MASK_TDTFM))) { + pr_debug("TDTF_IRQ\n"); + __raw_writel(SIM_XMT_STATUS_TDTF, sim->ioaddr + XMT_STATUS); + sim_xmt_fill(sim); + + if (sim->xmt_remaining == 0) { + __raw_writel(SIM_XMT_STATUS_TC, + sim->ioaddr + XMT_STATUS); + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data &= ~SIM_INT_MASK_TCIM; + reg_data |= SIM_INT_MASK_TDTFM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + }; + }; + + reg_data0 = __raw_readl(sim->ioaddr + RCV_STATUS); + reg_data1 = __raw_readl(sim->ioaddr + INT_MASK); + if ((reg_data0 & SIM_RCV_STATUS_RDRF) + && (!(reg_data1 & SIM_INT_MASK_RIM))) { + pr_debug("%s RDRF_IRQ\n", __func__); + __raw_writel(SIM_RCV_STATUS_RDRF, sim->ioaddr + RCV_STATUS); + + while (__raw_readl(sim->ioaddr + RCV_FIFO_CNT)) { + uint32_t data; + data = __raw_readl(sim->ioaddr + PORT1_RCV_BUF); + pr_debug("RX = %02x state = %i\n", data, sim->state); + if (data & 0x700) { + if (sim->xfer_ongoing) { + /* error */ + printk(KERN_ERR "ERROR !\n"); + return IRQ_HANDLED; + }; + } else + sim_fsm(sim, data); + }; + }; + + reg_data0 = __raw_readl(sim->ioaddr + PORT0_DETECT); + if (reg_data0 & SIM_PORT_DETECT_SDI) { + pr_debug("%s PD_IRQ\n", __func__); + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SDI; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + + reg_data0 = __raw_readl(sim->ioaddr + PORT0_DETECT); + if (reg_data0 & SIM_PORT_DETECT_SPDP) { + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data &= ~SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + + if (sim->present != SIM_PRESENT_REMOVED) { + pr_debug("Removed sim card\n"); + sim->present = SIM_PRESENT_REMOVED; + sim->state = SIM_STATE_REMOVED; + + if (sim->fasync) + kill_fasync(&sim->fasync, + SIGIO, POLL_IN); + }; + } else { + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + + if (sim->present == SIM_PRESENT_REMOVED) { + pr_debug("Inserted sim card\n"); + sim->state = SIM_STATE_DETECTED_ATR_T0; + sim->present = SIM_PRESENT_DETECTED; + + if (sim->fasync) + kill_fasync(&sim->fasync, + SIGIO, POLL_IN); + }; + }; + }; + + return IRQ_HANDLED; +}; + +/* Function: sim_power_on + * + * Description: run the power on sequence + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_power_on(sim_t *sim) +{ + uint32_t reg_data; + + /* power on sequence */ + pr_debug("%s Powering on the sim port.\n", __func__); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_SVEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + msleep(10); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_SCEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + msleep(10); + reg_data = SIM_RCV_THRESHOLD_RTH(0) | SIM_RCV_THRESHOLD_RDT(1); + __raw_writel(reg_data, sim->ioaddr + RCV_THRESHOLD); + __raw_writel(SIM_RCV_STATUS_RDRF, sim->ioaddr + RCV_STATUS); + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data &= ~SIM_INT_MASK_RIM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + __raw_writel(31, sim->ioaddr + DIVISOR); + reg_data = __raw_readl(sim->ioaddr + CNTL); + reg_data |= SIM_CNTL_SAMPLE12; + __raw_writel(reg_data, sim->ioaddr + CNTL); + reg_data = __raw_readl(sim->ioaddr + ENABLE); + reg_data |= SIM_ENABLE_RCVEN; + __raw_writel(reg_data, sim->ioaddr + ENABLE); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_SRST; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + pr_debug("%s port0_ctl is 0x%x.\n", __func__, + __raw_readl(sim->ioaddr + PORT0_CNTL)); + sim->power = SIM_POWER_ON; +}; + +/* Function: sim_power_off + * + * Description: run the power off sequence + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_power_off(sim_t *sim) +{ + uint32_t reg_data; + + pr_debug("%s entering.\n", __func__); + /* sim_power_off sequence */ + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data &= ~SIM_PORT_CNTL_SCEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + reg_data = __raw_readl(sim->ioaddr + ENABLE); + reg_data &= ~SIM_ENABLE_RCVEN; + __raw_writel(reg_data, sim->ioaddr + ENABLE); + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data |= SIM_INT_MASK_RIM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + __raw_writel(0, sim->ioaddr + RCV_THRESHOLD); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data &= ~SIM_PORT_CNTL_SRST; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data &= ~SIM_PORT_CNTL_SVEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + sim->power = SIM_POWER_OFF; +}; + +/* Function: sim_start + * + * Description: ramp up the SIM interface + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_start(sim_t *sim) +{ + uint32_t reg_data, clk_rate, clk_div = 0; + + pr_debug("%s entering.\n", __func__); + /* Configuring SIM for Operation */ + reg_data = SIM_XMT_THRESHOLD_XTH(0) | SIM_XMT_THRESHOLD_TDT(4); + __raw_writel(reg_data, sim->ioaddr + XMT_THRESHOLD); + __raw_writel(0, sim->ioaddr + SETUP); + /* ~ 4 MHz */ + clk_rate = clk_get_rate(sim->clk); + clk_div = clk_rate / sim->plat_data->clk_rate; + if (clk_rate % sim->plat_data->clk_rate) + clk_div++; + pr_debug("%s prescaler is 0x%x.\n", __func__, clk_div); + __raw_writel(clk_div, sim->ioaddr + CLK_PRESCALER); + + reg_data = SIM_CNTL_GPCNT_CLK_SEL(0) | SIM_CNTL_BAUD_SEL(7) + | SIM_CNTL_SAMPLE12 | SIM_CNTL_ANACK | SIM_CNTL_ICM; + __raw_writel(reg_data, sim->ioaddr + CNTL); + __raw_writel(31, sim->ioaddr + DIVISOR); + reg_data = __raw_readl(sim->ioaddr + OD_CONFIG); + reg_data |= SIM_OD_CONFIG_OD_P0; + __raw_writel(reg_data, sim->ioaddr + OD_CONFIG); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_3VOLT | SIM_PORT_CNTL_STEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + + /* presense detect */ + pr_debug("%s p0_det is 0x%x \n", __func__, + __raw_readl(sim->ioaddr + PORT0_DETECT)); + if (__raw_readl(sim->ioaddr + PORT0_DETECT) & SIM_PORT_DETECT_SPDP) { + pr_debug("%s card removed \n", __func__); + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data &= ~SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + sim->present = SIM_PRESENT_REMOVED; + sim->state = SIM_STATE_REMOVED; + } else { + pr_debug("%s card inserted \n", __func__); + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + sim->present = SIM_PRESENT_DETECTED; + sim->state = SIM_STATE_DETECTED_ATR_T0; + }; + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SDI; + reg_data &= ~SIM_PORT_DETECT_SDIM; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + + /* + * Since there is no PD0 layout on MX51, assume + * that there is a SIM card in slot defaulty. + * */ + if (0 == (sim->plat_data->detect)) { + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + sim->present = SIM_PRESENT_DETECTED; + sim->state = SIM_STATE_DETECTED_ATR_T0; + } + + if (sim->present == SIM_PRESENT_DETECTED) + sim_power_on(sim); + +}; + +/* Function: sim_stop + * + * Description: shut down the SIM interface + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_stop(sim_t *sim) +{ + pr_debug("%s entering.\n", __func__); + __raw_writel(0, sim->ioaddr + SETUP); + __raw_writel(0, sim->ioaddr + ENABLE); + __raw_writel(0, sim->ioaddr + PORT0_CNTL); + __raw_writel(0x06, sim->ioaddr + CNTL); + __raw_writel(0, sim->ioaddr + CLK_PRESCALER); + __raw_writel(0, sim->ioaddr + SETUP); + __raw_writel(0, sim->ioaddr + OD_CONFIG); + __raw_writel(0, sim->ioaddr + XMT_THRESHOLD); + __raw_writel(0xb8, sim->ioaddr + XMT_STATUS); + __raw_writel(4, sim->ioaddr + RESET_CNTL); + mdelay(1); +}; + +/* Function: sim_data_reset + * + * Description: reset a SIM structure to default values + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_data_reset(sim_t *sim) +{ + sim_param_t param_default = SIM_PARAM_DEFAULT; + sim->present = SIM_PRESENT_REMOVED; + sim->state = SIM_STATE_REMOVED; + sim->power = SIM_POWER_OFF; + sim->errval = SIM_OK; + memset(&sim->atrparser, 0, sizeof(sim->atrparser)); + memset(&sim->atr, 0, sizeof(sim->atr)); + sim->param_atr = param_default; + memset(&sim->param, 0, sizeof(sim->param)); + memset(&sim->xfer, 0, sizeof(sim->xfer)); + sim->xfer_ongoing = 0; + sim->xmt_remaining = 0; + sim->xmt_pos = 0; + sim->rcv_count = 0; + memset(sim->rcv_buffer, 0, SIM_RCV_BUFFER_SIZE); + memset(sim->xmt_buffer, 0, SIM_XMT_BUFFER_SIZE); +}; + +/* Function: sim_cold_reset + * + * Description: cold reset the SIM interface, including card + * power down and interface hardware reset. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_cold_reset(sim_t *sim) +{ + pr_debug("%s entering.\n", __func__); + if (sim->present != SIM_PRESENT_REMOVED) { + sim_power_off(sim); + sim_stop(sim); + sim_data_reset(sim); + sim->state = SIM_STATE_DETECTED_ATR_T0; + sim->present = SIM_PRESENT_DETECTED; + msleep(50); + sim_start(sim); + sim_power_on(sim); + }; +}; + +/* Function: sim_warm_reset + * + * Description: warm reset the SIM interface: just invoke the + * reset signal and reset the SIM structure for the interface. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_warm_reset(sim_t *sim) +{ + uint32_t reg_data; + + pr_debug("%s entering.\n", __func__); + if (sim->present != SIM_PRESENT_REMOVED) { + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_SRST; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + sim_data_reset(sim); + msleep(50); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data &= ~SIM_PORT_CNTL_SRST; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + }; +}; + +/* Function: sim_card_lock + * + * Description: physically lock the SIM card. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static int sim_card_lock(sim_t *sim) +{ + int errval; + + pr_debug("%s entering.\n", __func__); + /* place holder for true physcial locking */ + if (sim->present != SIM_PRESENT_REMOVED) + errval = SIM_OK; + else + errval = -SIM_E_NOCARD; + return errval; +}; + +/* Function: sim_card_eject + * + * Description: physically unlock and eject the SIM card. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static int sim_card_eject(sim_t *sim) +{ + int errval; + + pr_debug("%s entering.\n", __func__); + /* place holder for true physcial locking */ + if (sim->present != SIM_PRESENT_REMOVED) + errval = SIM_OK; + else + errval = -SIM_E_NOCARD; + return errval; +}; + +/* Function: sim_ioctl + * + * Description: handle ioctl calls + * + * Parameters: OS specific + */ + +static int sim_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret, errval = SIM_OK; + unsigned long timeout; + + sim_t *sim = (sim_t *) file->private_data; + + pr_debug("%s entering.\n", __func__); + switch (cmd) { + pr_debug("ioctl cmd %d is issued...\n", cmd); + + case SIM_IOCTL_GET_ATR: + if (sim->present != SIM_PRESENT_OPERATIONAL) { + errval = -SIM_E_NOCARD; + break; + }; + ret = copy_to_user((sim_atr_t *) arg, &sim->atr, + sizeof(sim_atr_t)); + if (ret) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_GET_PARAM_ATR: + if (sim->present != SIM_PRESENT_OPERATIONAL) { + errval = -SIM_E_NOCARD; + break; + }; + ret = copy_to_user((sim_param_t *) arg, &sim->param_atr, + sizeof(sim_param_t)); + if (ret) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_GET_PARAM: + ret = copy_to_user((sim_param_t *) arg, &sim->param, + sizeof(sim_param_t)); + if (ret) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_SET_PARAM: + ret = copy_from_user(&sim->param, (sim_param_t *) arg, + sizeof(sim_param_t)); + if (ret) + errval = -SIM_E_ACCESS; + else + errval = sim_set_param(sim, &sim->param); + break; + + case SIM_IOCTL_POWER_ON: + if (sim->power == SIM_POWER_ON) { + errval = -SIM_E_POWERED_ON; + break; + }; + sim_power_on(sim); + break; + + case SIM_IOCTL_POWER_OFF: + if (sim->power == SIM_POWER_OFF) { + errval = -SIM_E_POWERED_OFF; + break; + }; + sim_power_off(sim); + break; + + case SIM_IOCTL_COLD_RESET: + if (sim->power == SIM_POWER_OFF) { + errval = -SIM_E_POWERED_OFF; + break; + }; + sim_cold_reset(sim); + break; + + case SIM_IOCTL_WARM_RESET: + sim_warm_reset(sim); + if (sim->power == SIM_POWER_OFF) { + errval = -SIM_E_POWERED_OFF; + break; + }; + break; + + case SIM_IOCTL_XFER: + if (sim->present != SIM_PRESENT_OPERATIONAL) { + errval = -SIM_E_NOCARD; + break; + }; + + ret = copy_from_user(&sim->xfer, (sim_xfer_t *) arg, + sizeof(sim_xfer_t)); + if (ret) { + errval = -SIM_E_ACCESS; + break; + }; + + ret = copy_from_user(sim->xmt_buffer, sim->xfer.xmt_buffer, + sim->xfer.xmt_length); + if (ret) { + errval = -SIM_E_ACCESS; + break; + }; + + sim->rcv_count = 0; + sim->xfer.sw1 = 0; + sim->xfer.sw2 = 0; + + if (sim->xfer.type == SIM_XFER_TYPE_TPDU) { + if (sim->xfer.xmt_length < 5) { + errval = -SIM_E_TPDUSHORT; + break; + } + sim->state = SIM_STATE_OPERATIONAL_COMMAND; + } else if (sim->xfer.type == SIM_XFER_TYPE_PTS) { + if (sim->xfer.xmt_length == 0) { + errval = -SIM_E_PTSEMPTY; + break; + } + sim->state = SIM_STATE_OPERATIONAL_PTS; + } else { + errval = -SIM_E_INVALIDXFERTYPE; + break; + }; + + if (sim->xfer.xmt_length > SIM_XMT_BUFFER_SIZE) { + errval = -SIM_E_INVALIDXMTLENGTH; + break; + }; + + if (sim->xfer.rcv_length > SIM_XMT_BUFFER_SIZE) { + errval = -SIM_E_INVALIDRCVLENGTH; + break; + }; + + sim->errval = 0; + sim->xfer_ongoing = 1; + init_completion(&sim->xfer_done); + sim_xmt_start(sim, 0, 5); + timeout = + wait_for_completion_interruptible_timeout(&sim->xfer_done, + sim->xfer. + timeout); + sim->xfer_ongoing = 0; + + if (sim->errval) { + errval = sim->errval; + break; + }; + + if (timeout == 0) { + errval = -SIM_E_TIMEOUT; + break; + } + + ret = copy_to_user(sim->xfer.rcv_buffer, sim->rcv_buffer, + sim->xfer.rcv_length); + if (ret) { + errval = -SIM_E_ACCESS; + break; + }; + + ret = copy_to_user((sim_xfer_t *) arg, &sim->xfer, + sizeof(sim_xfer_t)); + if (ret) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_GET_PRESENSE: + if (put_user(sim->present, (int *)arg)) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_CARD_LOCK: + errval = sim_card_lock(sim); + break; + + case SIM_IOCTL_CARD_EJECT: + errval = sim_card_eject(sim); + break; + + }; + + return errval; +}; + +/* Function: sim_fasync + * + * Description: async handler + * + * Parameters: OS specific + */ + +static int sim_fasync(int fd, struct file *file, int mode) +{ + sim_t *sim = (sim_t *) file->private_data; + pr_debug("%s entering.\n", __func__); + return fasync_helper(fd, file, mode, &sim->fasync); +} + +/* Function: sim_open + * + * Description: ramp up interface when being opened + * + * Parameters: OS specific + */ + +static int sim_open(struct inode *inode, struct file *file) +{ + int errval = SIM_OK; + + sim_t *sim = dev_get_drvdata(sim_dev.parent); + file->private_data = sim; + + pr_debug("%s entering.\n", __func__); + if (!sim->ioaddr) { + errval = -ENOMEM; + return errval; + } + + if (!(sim->clk_flag)) { + pr_debug("\n%s enable the clock\n", __func__); + clk_enable(sim->clk); + sim->clk_flag = 1; + } + + sim_start(sim); + + return errval; +}; + +/* Function: sim_release + * + * Description: shut down interface when being closed + * + * Parameters: OS specific + */ + +static int sim_release(struct inode *inode, struct file *file) +{ + uint32_t reg_data; + + sim_t *sim = (sim_t *) file->private_data; + + pr_debug("%s entering.\n", __func__); + if (sim->clk_flag) { + pr_debug("\n%s disable the clock\n", __func__); + clk_disable(sim->clk); + sim->clk_flag = 0; + } + + /* disable presense detection */ + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + __raw_writel(reg_data | SIM_PORT_DETECT_SDIM, + sim->ioaddr + PORT0_DETECT); + + if (sim->present != SIM_PRESENT_REMOVED) { + sim_power_off(sim); + if (sim->fasync) + kill_fasync(&sim->fasync, SIGIO, POLL_IN); + }; + + sim_stop(sim); + + sim_fasync(-1, file, 0); + + pr_debug("exit\n"); + return 0; +}; + +static const struct file_operations sim_fops = { + .open = sim_open, + .ioctl = sim_ioctl, + .fasync = sim_fasync, + .release = sim_release +}; + +static struct miscdevice sim_dev = { + MISC_DYNAMIC_MINOR, + "mxc_sim", + &sim_fops +}; + +/*****************************************************************************\ + * * + * Driver init/exit * + * * +\*****************************************************************************/ + +static int sim_probe(struct platform_device *pdev) +{ + int ret = 0; + struct mxc_sim_platform_data *sim_plat = pdev->dev.platform_data; + + sim_t *sim = kzalloc(sizeof(sim_t), GFP_KERNEL); + + if (sim == 0) { + ret = -ENOMEM; + printk(KERN_ERR "Can't get the MEMORY\n"); + return ret; + }; + + BUG_ON(pdev == NULL); + + sim->plat_data = sim_plat; + sim->clk_flag = 0; + + sim->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!sim->res) { + ret = -ENOMEM; + printk(KERN_ERR "Can't get the MEMORY\n"); + goto out; + } + + /* request the sim clk and sim_serial_clk */ + sim->clk = clk_get(NULL, sim->plat_data->clock_sim); + if (IS_ERR(sim->clk)) { + ret = PTR_ERR(sim->clk); + printk(KERN_ERR "Get CLK ERROR !\n"); + goto out; + } + pr_debug("sim clock:%lu\n", clk_get_rate(sim->clk)); + + sim->ipb_irq = platform_get_irq(pdev, 0); + sim->dat_irq = platform_get_irq(pdev, 1); + if (!(sim->ipb_irq | sim->dat_irq)) { + ret = -ENOMEM; + goto out1; + } + + if (!request_mem_region(sim->res->start, + sim->res->end - + sim->res->start + 1, pdev->name)) { + printk(KERN_ERR "request_mem_region failed\n"); + ret = -ENOMEM; + goto out1; + } + + sim->ioaddr = (void *)ioremap(sim->res->start, sim->res->end - + sim->res->start + 1); + if (sim->ipb_irq) + ret = request_irq(sim->ipb_irq, sim_irq_handler, + 0, "mxc_sim_ipb", sim); + if (sim->dat_irq) + ret |= request_irq(sim->dat_irq, sim_irq_handler, + 0, "mxc_sim_dat", sim); + + if (ret) { + printk(KERN_ERR "Can't get the irq\n"); + goto out2; + }; + + platform_set_drvdata(pdev, sim); + sim_dev.parent = &(pdev->dev); + + misc_register(&sim_dev); + + return ret; +out2: + if (sim->ipb_irq) + free_irq(sim->ipb_irq, sim); + if (sim->dat_irq) + free_irq(sim->dat_irq, sim); + release_mem_region(sim->res->start, + sim->res->end - sim->res->start + 1); +out1: + clk_put(sim->clk); +out: + kfree(sim); + return ret; +} + +static int sim_remove(struct platform_device *pdev) +{ + sim_t *sim = platform_get_drvdata(pdev); + + clk_put(sim->clk); + + if (sim->ipb_irq) + free_irq(sim->ipb_irq, sim); + if (sim->dat_irq) + free_irq(sim->dat_irq, sim); + + iounmap(sim->ioaddr); + + kfree(sim); + release_mem_region(sim->res->start, + sim->res->end - sim->res->start + 1); + + misc_deregister(&sim_dev); + return 0; +} + +static struct platform_driver sim_driver = { + .driver = { + .name = "mxc_sim", + }, + .probe = sim_probe, + .remove = sim_remove, + .suspend = NULL, + .resume = NULL, +}; + +static int __init sim_drv_init(void) +{ + return platform_driver_register(&sim_driver); +} + +static void __exit sim_drv_exit(void) +{ + platform_driver_unregister(&sim_driver); +} + +module_init(sim_drv_init); +module_exit(sim_drv_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC SIM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/mxc_iim.c b/drivers/char/mxc_iim.c new file mode 100644 index 000000000000..b407d34759a9 --- /dev/null +++ b/drivers/char/mxc_iim.c @@ -0,0 +1,161 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/clk.h> +#include <linux/miscdevice.h> + +static unsigned long iim_reg_base, iim_reg_end, iim_reg_size; +static struct clk *iim_clk; +static struct device *iim_dev; + +/*! + * MXC IIM interface - memory map function + * This function maps 4KB IIM registers from IIM base address. + * + * @param file struct file * + * @param vma structure vm_area_struct * + * + * @return Return 0 on success or negative error code on error + */ +static int mxc_iim_mmap(struct file *file, struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */ + if (remap_pfn_range(vma, + vma->vm_start, + iim_reg_base >> PAGE_SHIFT, + iim_reg_size, + vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +/*! + * MXC IIM interface - open function + * + * @param inode struct inode * + * @param filp struct file * + * + * @return Return 0 on success or negative error code on error + */ +static int mxc_iim_open(struct inode *inode, struct file *filp) +{ + iim_clk = clk_get(NULL, "iim_clk"); + if (IS_ERR(iim_clk)) { + dev_err(iim_dev, "No IIM clock defined\n"); + return -ENODEV; + } + clk_enable(iim_clk); + + return 0; +} + +/*! + * MXC IIM interface - release function + * + * @param inode struct inode * + * @param filp struct file * + * + * @return Return 0 on success or negative error code on error + */ +static int mxc_iim_release(struct inode *inode, struct file *filp) +{ + clk_disable(iim_clk); + clk_put(iim_clk); + return 0; +} + +static const struct file_operations mxc_iim_fops = { + .mmap = mxc_iim_mmap, + .open = mxc_iim_open, + .release = mxc_iim_release, +}; + +static struct miscdevice mxc_iim_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mxc_iim", + .fops = &mxc_iim_fops, +}; + +/*! + * This function is called by the driver framework to get iim base/end address + * and register iim misc device. + * + * @param dev The device structure for IIM passed in by the driver + * framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int mxc_iim_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + iim_dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) { + dev_err(iim_dev, "Unable to get IIM resource\n"); + return -ENODEV; + } + + iim_reg_base = res->start; + iim_reg_end = res->end; + iim_reg_size = iim_reg_end - iim_reg_base + 1; + + ret = misc_register(&mxc_iim_miscdev); + if (ret) + return ret; + + return 0; +} + +static int mxc_iim_remove(struct platform_device *pdev) +{ + misc_deregister(&mxc_iim_miscdev); + return 0; +} + +static struct platform_driver mxc_iim_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mxc_iim", + }, + .probe = mxc_iim_probe, + .remove = mxc_iim_remove, +}; + +static int __init mxc_iim_dev_init(void) +{ + return platform_driver_register(&mxc_iim_driver); +} + +static void __exit mxc_iim_dev_cleanup(void) +{ + platform_driver_unregister(&mxc_iim_driver); +} + +module_init(mxc_iim_dev_init); +module_exit(mxc_iim_dev_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC IIM driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); diff --git a/drivers/char/mxc_si4702.c b/drivers/char/mxc_si4702.c new file mode 100644 index 000000000000..d9fcce6cb439 --- /dev/null +++ b/drivers/char/mxc_si4702.c @@ -0,0 +1,1221 @@ +/* + * linux/drivers/char/mxc_si4702.c + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/cdev.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/mxc_si4702.h> +#include <asm/uaccess.h> +#include <mach/hardware.h> + +#define SI4702_DEV_NAME "si4702" +#define DEV_MAJOR 0 /* this could be module param */ +#define DEV_MINOR 0 +#define DEV_BASE_MINOR 0 +#define DEV_MINOR_COUNT 256 +#define SI4702_I2C_ADDR 0x10 /* 7bits I2C address */ +#define DELAY_WAIT 0xffff /* loop_counter max value */ +/* register define */ +#define SI4702_DEVICEID 0x00 +#define SI4702_CHIPID 0x01 +#define SI4702_POWERCFG 0x02 +#define SI4702_CHANNEL 0x03 +#define SI4702_SYSCONFIG1 0x04 +#define SI4702_SYSCONFIG2 0x05 +#define SI4702_SYSCONFIG3 0x06 +#define SI4702_TEST1 0x07 +#define SI4702_TEST2 0x08 +#define SI4702_B00TCONFIG 0x09 +#define SI4702_STATUSRSSI 0x0A +#define SI4702_READCHAN 0x0B +#define SI4702_REG_NUM 0x10 +#define SI4702_REG_BYTE (SI4702_REG_NUM * 2) +#define SI4702_DEVICE_ID 0x1242 +#define SI4702_RW_REG_NUM (SI4702_STATUSRSSI - SI4702_POWERCFG) +#define SI4702_RW_OFFSET \ + (SI4702_REG_NUM - SI4702_STATUSRSSI + SI4702_POWERCFG) + +#define SI4702_SPACE_MASK 0x0030 +#define SI4702_SPACE_200K 0x0 +#define SI4702_SPACE_100K 0x10 +#define SI4702_SPACE_50K 0x20 + +#define SI4702_BAND_MASK 0x00c0 +#define SI4702_BAND_LSB 6 + +#define SI4702_SEEKTH_MASK 0xff00 +#define SI4702_SEEKTH_LSB 8 + +#define SI4702_SNR_MASK 0x00f0 +#define SI4702_SNR_LSB 4 + +#define SI4702_CNT_MASK 0x000f +#define SI4702_CNT_LSB 0 + +#define SI4702_VOL_MASK 0x000f +#define SI4702_VOL_LSB 0 + +#define SI4702_CHAN_MASK 0x03ff +#define SI4702_TUNE_BIT 0x8000 +#define SI4702_STC_BIT 0x4000 +#define SI4702_DMUTE_BIT 0x4000 +#define SI4702_SEEKUP_BIT 0x0200 +#define SI4702_SEEK_BIT 0x0100 +#define SI4702_SF_BIT 0x2000 +#define SI4702_ENABLE_BIT 0x0001 +#define SI4702_DISABLE_BIT 0x0040 + +enum { + BAND_USA = 0, + BAND_JAP_W, + BAND_JAP +}; + +struct si4702_info { + int min_band; + int max_band; + int space; + int volume; + int channel; + int mute; +}; + +struct si4702_drvdata { + struct regulator *vio; + struct regulator *vdd; + struct class *radio_class; + struct si4702_info info; + /*by default, dev major is zero, and it's alloc dynamicaly. */ + int major; + int minor; + struct cdev *cdev; + int count; /* open count */ + struct i2c_client *client; + unsigned char reg_rw_buf[SI4702_REG_BYTE]; + struct mxc_fm_platform_data *plat_data; +}; + +static struct si4702_drvdata *si4702_drvdata; + +DEFINE_SPINLOCK(count_lock); + +#ifdef DEBUG +static void si4702_dump_reg(void) +{ + int i, j; + unsigned char *reg_rw_buf; + + if (NULL == si4702_drvdata) + return; + + reg_rw_buf = si4702_drvdata->reg_rw_buf; + + for (i = 0; i < 10; i++) { + j = i * 2 + 12; + pr_debug("reg[%02d] = %04x\n", i, + ((reg_rw_buf[j] << 8) & 0xFF00) + + (reg_rw_buf[j + 1] & 0x00FF)); + } + for (; i < 16; i++) { + j = (i - 10) * 2; + pr_debug("reg[%02d] = %04x\n", i, + ((reg_rw_buf[j] << 8) & 0xFF00) + + (reg_rw_buf[j + 1] & 0x00FF)); + } +} +#else +static void si4702_dump_reg(void) +{ +} +#endif /* DEBUG */ + +/* + *check the si4702 spec for the read/write concequence. + * + *0 2 A F0 A F + *------------------------------- + * buf:0 2 A F + */ +#define REG_to_BUF(reg) (((reg >= 0) && (reg < SI4702_STATUSRSSI))?\ + (reg - SI4702_STATUSRSSI + SI4702_REG_NUM):\ + ((reg >= SI4702_STATUSRSSI) && (reg < SI4702_REG_NUM))?\ + (reg - SI4702_STATUSRSSI) : -1) + +static int si4702_read_reg(const int reg, u16 *value) +{ + int ret, index; + unsigned char *reg_rw_buf; + + if (NULL == si4702_drvdata) + return -1; + + reg_rw_buf = si4702_drvdata->reg_rw_buf; + + index = REG_to_BUF(reg); + + if (-1 == index) + return -1; + + ret = + i2c_master_recv(si4702_drvdata->client, reg_rw_buf, + SI4702_REG_BYTE); + + *value = (reg_rw_buf[index * 2] << 8) & 0xFF00; + *value |= reg_rw_buf[index * 2 + 1] & 0x00FF; + + return ret < 0 ? ret : 0; +} + +static int si4702_write_reg(const int reg, const u16 value) +{ + int index, ret; + unsigned char *reg_rw_buf; + + if (NULL == si4702_drvdata) + return -1; + + reg_rw_buf = si4702_drvdata->reg_rw_buf; + + index = REG_to_BUF(reg); + + if (-1 == index) + return -1; + + reg_rw_buf[index * 2] = (value & 0xFF00) >> 8; + reg_rw_buf[index * 2 + 1] = value & 0x00FF; + + ret = i2c_master_send(si4702_drvdata->client, + ®_rw_buf[SI4702_RW_OFFSET * 2], + (SI4702_STATUSRSSI - SI4702_POWERCFG) * 2); + return ret < 0 ? ret : 0; +} + +static void si4702_gpio_get(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->gpio_get(); +} + +static void si4702_gpio_put(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->gpio_put(); +} + +static void si4702_reset(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->reset(); +} + +static void si4702_clock_en(int flag) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->clock_ctl(flag); +} + +static int si4702_id_detect(struct i2c_client *client) +{ + int ret, index; + unsigned int ID = 0; + unsigned char reg_rw_buf[SI4702_REG_BYTE]; + + si4702_gpio_get(); + si4702_reset(); + si4702_clock_en(1); + + ret = i2c_master_recv(client, (char *)reg_rw_buf, SI4702_REG_BYTE); + + si4702_gpio_put(); + + if (ret < 0) + return ret; + + index = REG_to_BUF(SI4702_DEVICEID); + if (index < 0) + return index; + + ID = (reg_rw_buf[index * 2] << 8) & 0xFF00; + ID |= reg_rw_buf[index * 2 + 1] & 0x00FF; + + return ID; +} + +/* valid args 50/100/200 */ +static int si4702_set_space(int space) +{ + u16 reg; + int ret; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg &= ~SI4702_SPACE_MASK; + switch (space) { + case 50: + reg |= SI4702_SPACE_50K; + break; + case 100: + reg |= SI4702_SPACE_100K; + break; + case 200: + ret |= SI4702_SPACE_200K; + break; + default: + return -1; + } + + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + info = &si4702_drvdata->info; + info->space = space; + return 0; +} + +static int si4702_set_band_range(int band) +{ + u16 reg; + int ret, band_min, band_max; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + switch (band) { + case BAND_USA: + band_min = 87500; + band_max = 108000; + break; + case BAND_JAP_W: + band_min = 76000; + band_max = 108000; + break; + case BAND_JAP: + band_min = 76000; + band_max = 90000; + break; + default: + return -1; + } + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg = (reg & ~SI4702_BAND_MASK) + | ((band << SI4702_BAND_LSB) & SI4702_BAND_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + info = &si4702_drvdata->info; + info->min_band = band_min; + info->max_band = band_max; + return 0; +} + +static int si4702_set_seekth(u8 seekth) +{ + u16 reg; + int ret; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg = + (reg & ~SI4702_SEEKTH_MASK) | ((seekth << SI4702_SEEKTH_LSB) & + SI4702_SEEKTH_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + return 0; +} + +static int si4702_set_sksnr(u8 sksnr) +{ + u16 reg; + int ret; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG3, ®); + if (ret == -1) + return ret; + + reg = + (reg & ~SI4702_SNR_MASK) | ((sksnr << SI4702_SNR_LSB) & + SI4702_SNR_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG3, reg); + if (ret == -1) + return ret; + + return 0; +} + +static int si4702_set_skcnt(u8 skcnt) +{ + u16 reg; + int ret; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG3, ®); + if (ret == -1) + return ret; + + reg = (reg & ~SI4702_CNT_MASK) | (skcnt & SI4702_CNT_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG3, reg); + if (ret == -1) + return ret; + + return 0; +} + +static int si4702_set_vol(int vol) +{ + u16 reg; + int ret; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg = (reg & ~SI4702_VOL_MASK) | (vol & SI4702_VOL_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + info = &si4702_drvdata->info; + info->volume = vol; + + return 0; +} + +static u8 si4702_channel_select(u32 freq) +{ + u16 loop_counter = 0; + s16 channel; + u16 si4702_reg_data; + u8 error_ind = 0; + struct i2c_client *client; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + info = &si4702_drvdata->info; + client = si4702_drvdata->client; + + dev_info(&client->dev, "Input frequnce is %d\n", freq); + if (freq < 76000 || freq > 108000) { + dev_err(&client->dev, "Input frequnce is invalid\n"); + return -1; + } + /* convert freq to channel */ + channel = (freq - info->min_band) / info->space; + + si4702_reg_data = SI4702_TUNE_BIT | (channel & SI4702_CHAN_MASK); + /* set channel */ + error_ind = si4702_write_reg(SI4702_CHANNEL, si4702_reg_data); + if (error_ind) { + dev_err(&client->dev, "Failed to set channel\n"); + return -1; + } + dev_info(&client->dev, "Set channel to %d\n", channel); + + /* wait for STC == 1 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "Failed to read setted STC\n"); + return -1; + } + if ((si4702_reg_data & SI4702_STC_BIT) != 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* check loop_counter */ + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit set"); + return -1; + } + dev_info(&client->dev, "loop counter %d\n", loop_counter); + + loop_counter = 0; + /* clear tune bit */ + error_ind = si4702_write_reg(SI4702_CHANNEL, 0); + + if (error_ind) { + dev_err(&client->dev, "Failed to set stop tune\n"); + return -1; + } + + /* wait for STC == 0 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "Failed to set read STC\n"); + return -1; + } + if ((si4702_reg_data & SI4702_STC_BIT) == 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* check loop_counter */ + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit clean"); + return -1; + } + dev_info(&client->dev, "loop counter %d\n", loop_counter); + + /* read RSSI */ + error_ind = si4702_read_reg(SI4702_READCHAN, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "Failed to read RSSI\n"); + return -1; + } + + channel = si4702_reg_data & SI4702_CHAN_MASK; + dev_info(&client->dev, "seek finish: channel(%d)\n", channel); + + return 0; +} + +static s32 si4702_channel_seek(s16 dir) +{ + u16 loop_counter = 0; + u16 si4702_reg_data, reg_power_cfg; + u8 error_ind = 0; + u32 channel, freq; + struct i2c_client *client; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + info = &si4702_drvdata->info; + client = si4702_drvdata->client; + + error_ind = si4702_read_reg(SI4702_POWERCFG, ®_power_cfg); + + if (info->mute) { + /* check disable mute */ + reg_power_cfg &= ~SI4702_DMUTE_BIT; + } else { + reg_power_cfg |= SI4702_DMUTE_BIT; + } + + if (dir) { + reg_power_cfg |= SI4702_SEEKUP_BIT; + } else { + reg_power_cfg &= ~SI4702_SEEKUP_BIT; + } + /* start seek */ + reg_power_cfg |= SI4702_SEEK_BIT; + error_ind = si4702_write_reg(SI4702_POWERCFG, reg_power_cfg); + + if (error_ind) { + dev_err(&client->dev, "Failed to set seek start bit\n"); + return -1; + } + + /* wait STC == 1 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + if (error_ind) { + dev_err(&client->dev, "Failed to read STC bit\n"); + return -1; + } + + if ((si4702_reg_data & SI4702_STC_BIT) != 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* clear seek bit */ + reg_power_cfg &= ~SI4702_SEEK_BIT; + error_ind = si4702_write_reg(SI4702_POWERCFG, reg_power_cfg); + if (error_ind) { + dev_err(&client->dev, "Failed to stop seek\n"); + return -1; + } + + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit set\n"); + return -1; + } + + /* check whether SF==1 (seek failed bit) */ + if ((si4702_reg_data & SI4702_SF_BIT) != 0) { + dev_err(&client->dev, "Failed to seek any channel\n"); + return -1; + } + + loop_counter = 0; + /* wait STC == 0 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, + "Failed to wait STC bit to clear\n"); + return -1; + } + if ((si4702_reg_data & SI4702_STC_BIT) == 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* check loop_counter */ + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit clean"); + return -1; + } + + error_ind = si4702_read_reg(SI4702_READCHAN, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "I2C simulate failed\n"); + return -1; + } + + channel = si4702_reg_data & SI4702_CHAN_MASK; + freq = channel * info->space + info->min_band; + dev_err(&client->dev, + "seek finish: channel(%d), freq(%dKHz)\n", channel, freq); + + return 0; +} + +static int si4702_startup(void) +{ + u16 magic = 0, id; + struct i2c_client *client; + struct mxc_fm_platform_data *data; + + if (NULL == si4702_drvdata) + return -1; + + if (si4702_drvdata->vio) + regulator_enable(si4702_drvdata->vio); + if (si4702_drvdata->vdd) + regulator_enable(si4702_drvdata->vdd); + data = si4702_drvdata->plat_data; + client = si4702_drvdata->client; + + /* read prior to write, otherwise write op will fail */ + si4702_read_reg(SI4702_DEVICEID, &id); + dev_err(&client->dev, "si4702: DEVICEID: 0x%x\n", id); + + si4702_clock_en(1); + msleep(100); + + /* disable mute, stereo, seek down, powerup */ + si4702_write_reg(SI4702_POWERCFG, SI4702_DMUTE_BIT | SI4702_ENABLE_BIT); + msleep(500); + si4702_read_reg(SI4702_TEST1, &magic); + if (magic != 0x3C04) + dev_err(&client->dev, "magic number 0x%x.\n", magic); + /* close tune, set channel to 0 */ + si4702_write_reg(SI4702_CHANNEL, 0); + /* disable interrupt, disable GPIO */ + si4702_write_reg(SI4702_SYSCONFIG1, 0); + /* set volume to middle level */ + si4702_set_vol(0xf); + + si4702_set_space(data->space); + si4702_set_band_range(data->band); + si4702_set_seekth(data->seekth); + si4702_set_skcnt(data->skcnt); + si4702_set_sksnr(data->sksnr); + + return 0; +} + +static void si4702_shutdown(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_write_reg(SI4702_POWERCFG, SI4702_DMUTE_BIT | + SI4702_ENABLE_BIT | SI4702_DISABLE_BIT); + msleep(100); + si4702_clock_en(0); + + if (si4702_drvdata->vdd) + regulator_disable(si4702_drvdata->vdd); + if (si4702_drvdata->vio) + regulator_disable(si4702_drvdata->vio); +} + +enum { + FM_STARTUP = 0, + FM_SHUTDOWN, + FM_RESET, + FM_VOLUP, + FM_VOLDOWN, + FM_SEEK_UP, + FM_SEEK_DOWN, + FM_MUTEON, + FM_MUTEDIS, + FM_SEL, + FM_SEEKTH, + FM_DL, + FM_CTL_MAX +}; + +static const char *const fm_control[FM_CTL_MAX] = { + [FM_STARTUP] = "start", + [FM_SHUTDOWN] = "halt", + [FM_RESET] = "reset", + [FM_VOLUP] = "volup", + [FM_VOLDOWN] = "voldown", + [FM_SEEK_UP] = "seeku", + [FM_SEEK_DOWN] = "seekd", + [FM_MUTEON] = "mute", + [FM_MUTEDIS] = "muted", + [FM_SEL] = "select", + [FM_SEEKTH] = "seekth", + [FM_DL] = "delay" +}; + +static int cmd(unsigned int index, int arg) +{ + struct i2c_client *client; + struct mxc_fm_platform_data *plat_data; + + if (NULL == si4702_drvdata) + return -1; + + client = si4702_drvdata->client; + plat_data = si4702_drvdata->plat_data; + + switch (index) { + case FM_SHUTDOWN: + dev_err(&client->dev, "FM_SHUTDOWN\n"); + si4702_shutdown(); + break; + case FM_STARTUP: + dev_err(&client->dev, "FM_STARTUP\n"); + si4702_reset(); + si4702_startup(); + break; + case FM_RESET: + dev_err(&client->dev, "FM_RESET\n"); + si4702_reset(); + break; + case FM_SEEK_DOWN: + dev_err(&client->dev, "SEEK DOWN\n"); + si4702_channel_seek(0); + break; + case FM_SEEK_UP: + dev_err(&client->dev, "SEEK UP\n"); + si4702_channel_seek(1); + break; + case FM_SEL: + dev_err(&client->dev, "select %d\n", arg * 100); + si4702_channel_select(arg * 100); + break; + case FM_SEEKTH: + dev_err(&client->dev, "seekth = %d\n", arg); + si4702_set_seekth(arg); + break; + case FM_DL: + dev_err(&client->dev, "delay = %d\n", arg); + break; + default: + dev_err(&client->dev, "error command\n"); + break; + } + return 0; +} + +static ssize_t si4702_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct si4702_drvdata *drv_data = dev_get_drvdata(dev); + u16 device_id; + + dev_err(&(drv_data->client->dev), "si4702 show\n"); + si4702_read_reg(SI4702_DEVICEID, &device_id); + pr_info("device id %x\n", device_id); + si4702_dump_reg(); + return 0; +} + +static ssize_t si4702_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int state = 0; + const char *const *s; + char *p = NULL; + int error; + int len, arg = 0; + struct si4702_drvdata *drv_data = dev_get_drvdata(dev); + struct i2c_client *client = drv_data->client; + + dev_err(&client->dev, "si4702 store %d\n", count); + + p = memchr(buf, ' ', count); + if (p) { + len = p - buf; + *p = '\0'; + } else + len = count; + + len -= 1; + dev_err(&client->dev, "cmd %s\n", buf); + + for (s = &fm_control[state]; state < FM_CTL_MAX; s++, state++) { + if (*s && !strncmp(buf, *s, len)) { + break; + } + } + if (state < FM_CTL_MAX && *s) { + if (p) + arg = simple_strtoul(p + 1, NULL, 0); + dev_err(&client->dev, "arg = %d\n", arg); + error = cmd(state, arg); + } else { + dev_err(&client->dev, "error cmd\n"); + error = -EINVAL; + } + + return error ? error : count; +} + +static int ioctl_si4702(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int mute = 0; + u16 data; + int error; + u8 volume; + unsigned int freq; + int dir; + struct i2c_client *client; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + info = &si4702_drvdata->info; + client = si4702_drvdata->client; + + dev_err(&client->dev, "ioctl, cmd: 0x%x, arg: 0x%lx\n", cmd, arg); + + switch (cmd) { + case SI4702_SETVOLUME: + /* get volume from user */ + if (copy_from_user(&volume, argp, sizeof(u8))) { + + dev_err(&client->dev, + "ioctl, copy volume value from user failed\n"); + return -EFAULT; + } + dev_err(&client->dev, "volume %d\n", volume); + /* refill the register value */ + volume &= 0x0f; + if (info->mute) + error = si4702_write_reg(SI4702_POWERCFG, 0x0001); + else + error = si4702_write_reg(SI4702_POWERCFG, 0x4001); + + error = si4702_write_reg(SI4702_CHANNEL, 0); + error = si4702_write_reg(SI4702_SYSCONFIG1, 0); + error = si4702_write_reg(SI4702_SYSCONFIG2, 0x0f10 | volume); + if (error) { + dev_err(&client->dev, "ioctl, set volume failed\n"); + return -EFAULT; + } + /* renew the device info */ + info->volume = volume; + + break; + case SI4702_GETVOLUME: + /* just copy volume value to user */ + if (copy_to_user(argp, &(info->volume), sizeof(unsigned int))) { + dev_err(&client->dev, "ioctl, copy to user failed\n"); + return -EFAULT; + } + break; + case SI4702_MUTEON: + mute = 1; + case SI4702_MUTEOFF: + if (mute) { + /* enable mute */ + si4702_read_reg(SI4702_POWERCFG, &data); + data &= 0x00FF; + error = si4702_write_reg(SI4702_POWERCFG, data); + } else { + si4702_read_reg(SI4702_POWERCFG, &data); + data &= 0x00FF; + data |= 0x4000; + error = si4702_write_reg(SI4702_POWERCFG, data); + } + if (error) { + dev_err(&client->dev, "ioctl, set mute failed\n"); + return -EFAULT; + } + break; + case SI4702_SELECT: + if (copy_from_user(&freq, argp, sizeof(unsigned int))) { + + dev_err(&client->dev, + "ioctl, copy frequence from user failed\n"); + return -EFAULT; + } + /* check frequence */ + if (freq > info->max_band || freq < info->min_band) { + dev_err(&client->dev, + "the frequence select is out of band\n"); + return -EINVAL; + } + if (si4702_channel_select(freq)) { + dev_err(&client->dev, + "ioctl, failed to select channel\n"); + return -EFAULT; + } + break; + case SI4702_SEEK: + if (copy_from_user(&dir, argp, sizeof(int))) { + + dev_err(&client->dev, "ioctl, copy from user failed\n"); + return -EFAULT; + } + /* get seeked channel */ + dir = si4702_channel_seek(dir); + if (dir == -1) { + return -EAGAIN; + } else if (dir == -2) { + return -EFAULT; + } + if (copy_to_user(argp, &dir, sizeof(int))) { + + dev_err(&client->dev, + "ioctl, copy seek frequnce to user failed\n"); + return -EFAULT; + } + break; + default: + dev_err(&client->dev, "SI4702: Invalid ioctl command\n"); + return -EINVAL; + + } + return 0; +} + +static int open_si4702(struct inode *inode, struct file *file) +{ + struct i2c_client *client; + + if (NULL == si4702_drvdata) + return -1; + + client = si4702_drvdata->client; + + spin_lock(&count_lock); + if (si4702_drvdata->count != 0) { + dev_err(&client->dev, "device has been open already\n"); + spin_unlock(&count_lock); + return -EBUSY; + } + si4702_drvdata->count++; + spin_unlock(&count_lock); + + /* request and active GPIO */ + si4702_gpio_get(); + /* reset the si4702 from it's reset pin */ + si4702_reset(); + + /* startup si4702 */ + if (si4702_startup()) { + spin_lock(&count_lock); + si4702_drvdata->count--; + spin_unlock(&count_lock); + return -ENODEV; + } + + return 0; +} + +static int release_si4702(struct inode *inode, struct file *file) +{ + struct i2c_client *client; + + if (NULL == si4702_drvdata) + return -1; + + client = si4702_drvdata->client; + + dev_err(&client->dev, "release\n"); + /* software shutdown */ + si4702_shutdown(); + /* inactive, free GPIO, cut power */ + si4702_gpio_put(); + + spin_lock(&count_lock); + si4702_drvdata->count--; + spin_unlock(&count_lock); + + return 0; +} + +static int si4702_suspend(struct i2c_client *client, pm_message_t state) +{ + return 0; +} + +static int si4702_resume(struct i2c_client *client) +{ + return 0; +} + +static struct device_attribute si4702_dev_attr = { + .attr = { + .name = "si4702_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = si4702_show, + .store = si4702_store, +}; + +static struct file_operations si4702_fops = { + .owner = THIS_MODULE, + .open = open_si4702, + .release = release_si4702, + .ioctl = ioctl_si4702, +}; + +static int __devinit si4702_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mxc_fm_platform_data *plat_data; + struct si4702_drvdata *drv_data; + struct device *dev; + + dev_info(&client->dev, "si4702 device probe process start.\n"); + + plat_data = (struct mxc_fm_platform_data *)client->dev.platform_data; + if (plat_data == NULL) { + dev_err(&client->dev, "lack of platform data!\n"); + return -ENODEV; + } + + drv_data = kmalloc(sizeof(struct si4702_drvdata), GFP_KERNEL); + if (drv_data == NULL) { + dev_err(&client->dev, "lack of kernel memory!\n"); + return -ENOMEM; + } + memset(drv_data, 0, sizeof(struct si4702_drvdata)); + drv_data->plat_data = plat_data; + drv_data->major = DEV_MAJOR; + drv_data->minor = DEV_MINOR; + drv_data->count = 0; + + /*enable power supply */ + if (plat_data->reg_vio != NULL) { + drv_data->vio = regulator_get(&client->dev, plat_data->reg_vio); + if (drv_data->vio == ERR_PTR(-ENOENT)) + goto free_drv_data; + regulator_enable(drv_data->vio); + } + + /* here, we assume that vio and vdd are not the same */ + if (plat_data->reg_vdd != NULL) { + drv_data->vdd = regulator_get(&client->dev, plat_data->reg_vdd); + if (drv_data->vdd == ERR_PTR(-ENOENT)) + goto disable_vio; + regulator_enable(drv_data->vdd); + } + + /*attach client and check device id */ + if (SI4702_DEVICE_ID != si4702_id_detect(client)) { + dev_err(&client->dev, "id wrong.\n"); + goto disable_vdd; + } + dev_info(&client->dev, "chip id %x detect.\n", SI4702_DEVICE_ID); + drv_data->client = client; + + /*user interface begain */ + /*create device file in sysfs as a user interface, + * also for debug support */ + ret = device_create_file(&client->dev, &si4702_dev_attr); + if (ret) { + dev_err(&client->dev, "create device file failed!\n"); + goto gpio_put; /* shall i use some meanful error code? */ + } + + /*create a char dev for application code access */ + if (drv_data->major) { + ret = register_chrdev(drv_data->major, "si4702", &si4702_fops);; + } else { + ret = register_chrdev(0, "si4702", &si4702_fops); + } + + if (drv_data->major == 0) + drv_data->major = ret; + + /* create class and device for udev information */ + drv_data->radio_class = class_create(THIS_MODULE, "radio"); + if (IS_ERR(drv_data->radio_class)) { + dev_err(&client->dev, "SI4702: failed to create radio class\n"); + goto char_dev_remove; + } + + dev = device_create(drv_data->radio_class, NULL, + MKDEV(drv_data->major, drv_data->minor), NULL, "si4702"); + if (IS_ERR(dev)) { + dev_err(&client->dev, + "SI4702: failed to create radio class device\n"); + goto class_remove; + } + /*User interface end */ + dev_set_drvdata(&client->dev, drv_data); + si4702_drvdata = drv_data; + + si4702_gpio_get(); + dev_info(&client->dev, "si4702 device probe successfully.\n"); + si4702_shutdown(); + + return 0; + +class_remove: + class_destroy(drv_data->radio_class); +char_dev_remove: + unregister_chrdev(drv_data->major, "si4702"); + device_remove_file(&client->dev, &si4702_dev_attr); +gpio_put: + si4702_gpio_put(); +disable_vdd: + if (plat_data->reg_vdd) { + regulator_disable(drv_data->vdd); + regulator_put(drv_data->vdd); + } +disable_vio: + if (plat_data->reg_vio) { + regulator_disable(drv_data->vio); + regulator_put(drv_data->vio); + } + +free_drv_data: + kfree(drv_data); + + return -ENODEV; +} + +static int __devexit si4702_remove(struct i2c_client *client) +{ + struct mxc_fm_platform_data *plat_data; + struct si4702_drvdata *drv_data = dev_get_drvdata(&client->dev); + + plat_data = (struct mxc_fm_platform_data *)client->dev.platform_data; + + device_destroy(drv_data->radio_class, + MKDEV(drv_data->major, drv_data->minor)); + class_destroy(drv_data->radio_class); + + unregister_chrdev(drv_data->major, "si4702"); + device_remove_file(&client->dev, &si4702_dev_attr); + si4702_gpio_put(); + + if (plat_data->reg_vdd) + regulator_put(drv_data->vdd); + + if (plat_data->reg_vio) + regulator_put(drv_data->vio); + + kfree(si4702_drvdata); + si4702_drvdata = NULL; + + return 0; +} + +static const struct i2c_device_id si4702_id[] = { + {"si4702", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, si4702_id); + +static struct i2c_driver i2c_si4702_driver = { + .driver = { + .name = "si4702", + }, + .probe = si4702_probe, + .remove = si4702_remove, + .suspend = si4702_suspend, + .resume = si4702_resume, + .id_table = si4702_id, +}; + +static int __init init_si4702(void) +{ + /*add to i2c driver */ + pr_info("add si4702 i2c driver\n"); + return i2c_add_driver(&i2c_si4702_driver); +} + +static void __exit exit_si4702(void) +{ + i2c_del_driver(&i2c_si4702_driver); +} + +module_init(init_si4702); +module_exit(exit_si4702); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SI4702 FM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/mxs_viim.c b/drivers/char/mxs_viim.c new file mode 100644 index 000000000000..2510afa6c13e --- /dev/null +++ b/drivers/char/mxs_viim.c @@ -0,0 +1,175 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> + +static unsigned long iim_reg_base0, iim_reg_end0, iim_reg_size0; +static unsigned long iim_reg_base1, iim_reg_end1, iim_reg_size1; +static struct device *iim_dev; + +/*! + * MXS Virtual IIM interface - memory map function + * This function maps one page size VIIM registers from VIIM base address0 + * if the size of the required virtual memory space is less than or equal to + * one page size, otherwise this function will also map one page size VIIM + * registers from VIIM base address1. + * + * @param file struct file * + * @param vma structure vm_area_struct * + * + * @return Return 0 on success or negative error code on error + */ +static int mxs_viim_mmap(struct file *file, struct vm_area_struct *vma) +{ + size_t size = vma->vm_end - vma->vm_start; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */ + if (remap_pfn_range(vma, + vma->vm_start, + iim_reg_base0 >> PAGE_SHIFT, + iim_reg_size0, + vma->vm_page_prot)) + return -EAGAIN; + + if (size > iim_reg_size0) { + if (remap_pfn_range(vma, + vma->vm_start + iim_reg_size0, + iim_reg_base1 >> PAGE_SHIFT, + iim_reg_size1, + vma->vm_page_prot)) + return -EAGAIN; + } + + return 0; +} + +/*! + * MXS Virtual IIM interface - open function + * + * @param inode struct inode * + * @param filp struct file * + * + * @return Return 0 on success or negative error code on error + */ +static int mxs_viim_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +/*! + * MXS Virtual IIM interface - release function + * + * @param inode struct inode * + * @param filp struct file * + * + * @return Return 0 on success or negative error code on error + */ +static int mxs_viim_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +static const struct file_operations mxs_viim_fops = { + .mmap = mxs_viim_mmap, + .open = mxs_viim_open, + .release = mxs_viim_release, +}; + +static struct miscdevice mxs_viim_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mxs_viim", + .fops = &mxs_viim_fops, +}; + +/*! + * This function is called by the driver framework to get virtual iim base/end + * address and register iim misc device. + * + * @param dev The device structure for Virtual IIM passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int mxs_viim_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + iim_dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) { + dev_err(iim_dev, "Unable to get Virtual IIM resource 0\n"); + return -ENODEV; + } + + iim_reg_base0 = res->start; + iim_reg_end0 = res->end; + iim_reg_size0 = iim_reg_end0 - iim_reg_base0 + 1; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (IS_ERR(res)) { + dev_err(iim_dev, "Unable to get Virtual IIM resource 1\n"); + return -ENODEV; + } + + iim_reg_base1 = res->start; + iim_reg_end1 = res->end; + iim_reg_size1 = iim_reg_end1 - iim_reg_base1 + 1; + + ret = misc_register(&mxs_viim_miscdev); + if (ret) + return ret; + + return 0; +} + +static int mxs_viim_remove(struct platform_device *pdev) +{ + misc_deregister(&mxs_viim_miscdev); + return 0; +} + +static struct platform_driver mxs_viim_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mxs_viim", + }, + .probe = mxs_viim_probe, + .remove = mxs_viim_remove, +}; + +static int __init mxs_viim_dev_init(void) +{ + return platform_driver_register(&mxs_viim_driver); +} + +static void __exit mxs_viim_dev_cleanup(void) +{ + platform_driver_unregister(&mxs_viim_driver); +} + +module_init(mxs_viim_dev_init); +module_exit(mxs_viim_dev_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXS Virtual IIM driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); |