diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/misc/c2c/samsung-c2c.c | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip |
samsung update 1
Diffstat (limited to 'drivers/misc/c2c/samsung-c2c.c')
-rw-r--r-- | drivers/misc/c2c/samsung-c2c.c | 712 |
1 files changed, 712 insertions, 0 deletions
diff --git a/drivers/misc/c2c/samsung-c2c.c b/drivers/misc/c2c/samsung-c2c.c new file mode 100644 index 00000000000..fe58c3d2131 --- /dev/null +++ b/drivers/misc/c2c/samsung-c2c.c @@ -0,0 +1,712 @@ +/* + * Samsung C2C driver + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Author: Kisang Lee <kisang80.lee@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/cma.h> +#include <linux/sysfs.h> +#ifdef ENABLE_C2CSTATE_TIMER +#include <linux/timer.h> +#endif +#ifdef CONFIG_C2C_IPC_ENABLE +#include <linux/vmalloc.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#endif +#include <asm/mach-types.h> + +#include <mach/c2c.h> +#include <mach/regs-c2c.h> +#include <mach/regs-pmu.h> +#include <mach/regs-pmu5.h> +#include <mach/pmu.h> +#include <plat/cpu.h> + +#include "samsung-c2c.h" + +void (*exynos_c2c_request_pwr_mode)(enum c2c_pwr_mode mode); + +#ifdef ENABLE_C2CSTATE_TIMER +struct timer_list c2c_status_timer; + +static void c2c_timer_func(unsigned long data) +{ + /* Check C2C state */ + struct exynos_c2c_platdata *pdata = (struct exynos_c2c_platdata *)data; + static int old_state = 0xff; + int current_state = 0; + + if (pdata->get_c2c_state() != NULL) { + current_state = pdata->get_c2c_state(); + if (current_state != old_state) { + dev_info(c2c_con.c2c_dev, "C2C state is chaged (0x%x --> 0x%x)\n", + old_state, current_state); + old_state = current_state; + } + } + c2c_status_timer.expires = jiffies + (HZ/5); + add_timer(&c2c_status_timer); +} +#endif + +void c2c_reset_ops(void) +{ + /* This function will be only used for EVT0 or EVT0.1 */ + u32 set_clk = 0; + + if (c2c_con.opp_mode == C2C_OPP100) + set_clk = c2c_con.clk_opp100; + else if (c2c_con.opp_mode == C2C_OPP50) + set_clk = c2c_con.clk_opp50; + else if (c2c_con.opp_mode == C2C_OPP25) + set_clk = c2c_con.clk_opp25; + + dev_info(c2c_con.c2c_dev, "c2c_reset_ops()\n"); + clk_set_rate(c2c_con.c2c_sclk, (set_clk + 1) * MHZ); + c2c_set_func_clk(set_clk); + + /* First phase - C2C block reset */ + c2c_set_reset(C2C_CLEAR); + c2c_set_reset(C2C_SET); + /* Second phase - Clear clock gating */ + c2c_set_clock_gating(C2C_CLEAR); + /* Third phase - Retention reg */ + c2c_writel(c2c_con.retention_reg, EXYNOS_C2C_IRQ_EN_SET1); + c2c_writel(set_clk, EXYNOS_C2C_FCLK_FREQ); + c2c_writel(set_clk, EXYNOS_C2C_RX_MAX_FREQ); + /* Last phase - Set clock gating */ + c2c_set_clock_gating(C2C_SET); +} + +static ssize_t c2c_ctrl_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret = 0; + ret = sprintf(buf, "C2C State"); + c2c_set_clock_gating(C2C_CLEAR); + ret += sprintf(&buf[ret], "SysReg : 0x%x\n", + readl(c2c_con.c2c_sysreg)); + ret += sprintf(&buf[ret], "Port Config : 0x%x\n", + c2c_readl(EXYNOS_C2C_PORTCONFIG)); + ret += sprintf(&buf[ret], "FCLK_FREQ : %d\n", + c2c_readl(EXYNOS_C2C_FCLK_FREQ)); + ret += sprintf(&buf[ret], "RX_MAX_FREQ : %d\n", + c2c_readl(EXYNOS_C2C_RX_MAX_FREQ)); + ret += sprintf(&buf[ret], "IRQ_EN_SET1 : 0x%x\n", + c2c_readl(EXYNOS_C2C_IRQ_EN_SET1)); + ret += sprintf(&buf[ret], "Get C2C sclk rate : %ld\n", + clk_get_rate(c2c_con.c2c_sclk)); + ret += sprintf(&buf[ret], "Get C2C aclk rate : %ld\n", + clk_get_rate(c2c_con.c2c_aclk)); + c2c_set_clock_gating(C2C_SET); + + return ret; +} + +static ssize_t c2c_ctrl_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ops_num, opp_val, req_clk; + sscanf(buf, "%d", &ops_num); + + switch (ops_num) { + case 1: + c2c_reset_ops(); + break; + case 2: + case 3: + case 4: + opp_val = ops_num - 1; + req_clk = 0; + dev_info(c2c_con.c2c_dev, "Set current OPP mode (%d)\n", opp_val); + + if (opp_val == C2C_OPP100) + req_clk = c2c_con.clk_opp100; + else if (opp_val == C2C_OPP50) + req_clk = c2c_con.clk_opp50; + else if (opp_val == C2C_OPP25) + req_clk = c2c_con.clk_opp25; + + if (opp_val == 0 || req_clk == 1) { + dev_info(c2c_con.c2c_dev, "This mode is not reserved in OPP mode.\n"); + } else { + c2c_set_clock_gating(C2C_CLEAR); + if (c2c_con.opp_mode < opp_val) { /* increase case */ + clk_set_rate(c2c_con.c2c_sclk, (req_clk + 1) * MHZ); + c2c_writel(req_clk, EXYNOS_C2C_FCLK_FREQ); + c2c_set_func_clk(req_clk); + c2c_writel(req_clk, EXYNOS_C2C_RX_MAX_FREQ); + } else if (c2c_con.opp_mode > opp_val) { /* decrease case */ + c2c_writel(req_clk, EXYNOS_C2C_RX_MAX_FREQ); + clk_set_rate(c2c_con.c2c_sclk, (req_clk + 1) * MHZ); + c2c_writel(req_clk, EXYNOS_C2C_FCLK_FREQ); + c2c_set_func_clk(req_clk); + } else{ + dev_info(c2c_con.c2c_dev, "Requested same OPP mode\n"); + } + c2c_con.opp_mode = opp_val; + c2c_set_clock_gating(C2C_SET); + } + + dev_info(c2c_con.c2c_dev, "Get C2C sclk rate : %ld\n", + clk_get_rate(c2c_con.c2c_sclk)); + dev_info(c2c_con.c2c_dev, "Get C2C aclk rate : %ld\n", + clk_get_rate(c2c_con.c2c_aclk)); + break; + default: + dev_info(c2c_con.c2c_dev, "Wrong C2C operation number\n"); + dev_info(c2c_con.c2c_dev, "---C2C Operation Number---\n"); + dev_info(c2c_con.c2c_dev, "1. C2C Reset\n"); + dev_info(c2c_con.c2c_dev, "2. Set OPP25\n"); + dev_info(c2c_con.c2c_dev, "3. Set OPP50\n"); + dev_info(c2c_con.c2c_dev, "4. Set OPP100\n"); + } + + return count; +} + +static DEVICE_ATTR(c2c_ctrl, 0644, c2c_ctrl_show, c2c_ctrl_store); + +int c2c_open(struct inode *inode, struct file *filp) +{ + /* This function is not needed.(Test Function) */ + dev_info(c2c_con.c2c_dev, "C2C chrdrv Opened.\n"); + + return 0; +} + +static long c2c_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + c2c_reset_ops(); + + return 0; +} + +static const struct file_operations c2c_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = c2c_ioctl, + .open = c2c_open, +}; + +static struct miscdevice char_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = C2C_DEV_NAME, + .fops = &c2c_fops +}; + +static int c2c_set_sharedmem(enum c2c_shrdmem_size size, u32 addr) +{ + dev_info(c2c_con.c2c_dev, "Set BaseAddr(0x%x) and Size(%d)\n", + addr, 1 << (2 + size)); + + /* Set DRAM Base Addr & Size */ + c2c_set_shdmem_size(size); + c2c_set_base_addr((addr >> 22)); + + return 0; +} + +static void c2c_set_interrupt(u32 genio_num, enum c2c_interrupt set_int) +{ + u32 cur_int_reg, cur_lev_reg; + + cur_int_reg = c2c_readl(EXYNOS_C2C_GENO_INT); + cur_lev_reg = c2c_readl(EXYNOS_C2C_GENO_LEVEL); + + switch (set_int) { + case C2C_INT_TOGGLE: + cur_int_reg &= ~(0x1 << genio_num); + c2c_writel(cur_int_reg, EXYNOS_C2C_GENO_INT); + break; + case C2C_INT_HIGH: + cur_int_reg |= (0x1 << genio_num); + cur_lev_reg |= (0x1 << genio_num); + c2c_writel(cur_int_reg, EXYNOS_C2C_GENO_INT); + c2c_writel(cur_lev_reg, EXYNOS_C2C_GENO_LEVEL); + break; + case C2C_INT_LOW: + cur_int_reg |= (0x1 << genio_num); + cur_lev_reg &= ~(0x1 << genio_num); + c2c_writel(cur_int_reg, EXYNOS_C2C_GENO_INT); + c2c_writel(cur_lev_reg, EXYNOS_C2C_GENO_LEVEL); + break; + } +} + +static irqreturn_t c2c_sscm0_irq(int irq, void *data) +{ + /* TODO : This function will be used other type boards */ + return IRQ_HANDLED; +} + +static irqreturn_t c2c_sscm1_irq(int irq, void *data) +{ + /* TODO : It is just temporary code. It will be modified. */ + u32 raw_irq, latency_val, opp_val, req_clk; + raw_irq = c2c_readl(EXYNOS_C2C_IRQ_EN_STAT1); + +#ifdef CONFIG_C2C_IPC_ENABLE + if (raw_irq & 0x1) { + dev_info(c2c_con.c2c_dev, "IPC interrupt occured : GENO[0]\n"); + if (c2c_con.hd.handler) + c2c_con.hd.handler(c2c_con.hd.data); + + /* Interrupt Clear */ + c2c_writel(0x1, EXYNOS_C2C_IRQ_EN_STAT1); + } +#endif + if ((raw_irq >> C2C_GENIO_OPP_INT) & 1) { /* OPP Change */ + /* + OPP mode GENI/O bit definition[29:27] + OPP100 GENI/O[29:28] : 1 1 + OPP50 GENI/O[29:28] : 1 0 + OPP25 GENI/O[29:28] : 0 1 + GENI[27] is only used for making interrupt. + */ + opp_val = (c2c_readl(EXYNOS_C2C_GENO_STATUS) >> 28) & 3; + req_clk = 0; + dev_info(c2c_con.c2c_dev, "OPP interrupt occured (%d)\n", opp_val); + + if (opp_val == C2C_OPP100) + req_clk = c2c_con.clk_opp100; + else if (opp_val == C2C_OPP50) + req_clk = c2c_con.clk_opp50; + else if (opp_val == C2C_OPP25) + req_clk = c2c_con.clk_opp25; + + if (opp_val == 0 || req_clk == 1) { + dev_info(c2c_con.c2c_dev, "This mode is not reserved in OPP mode.\n"); + } else { + if (c2c_con.opp_mode < opp_val) { /* increase case */ + clk_set_rate(c2c_con.c2c_sclk, (req_clk + 1) * MHZ); + c2c_writel(req_clk, EXYNOS_C2C_FCLK_FREQ); + c2c_set_func_clk(req_clk); + c2c_writel(req_clk, EXYNOS_C2C_RX_MAX_FREQ); + } else if (c2c_con.opp_mode > opp_val) { /* decrease case */ + c2c_writel(req_clk, EXYNOS_C2C_RX_MAX_FREQ); + clk_set_rate(c2c_con.c2c_sclk, (req_clk + 1) * MHZ); + c2c_writel(req_clk, EXYNOS_C2C_FCLK_FREQ); + c2c_set_func_clk(req_clk); + } else{ + dev_info(c2c_con.c2c_dev, "Requested same OPP mode\n"); + } + c2c_con.opp_mode = opp_val; + } + + /* Interrupt Clear */ + c2c_writel((0x1 << C2C_GENIO_OPP_INT), EXYNOS_C2C_IRQ_EN_STAT1); + } + + /* Memory I/F latency change */ + if ((raw_irq >> C2C_GENIO_LATENCY_INT) & 1) { + latency_val = (c2c_readl(EXYNOS_C2C_GENO_STATUS) >> 30) & 3; + switch (latency_val) { + case 3: + dev_info(c2c_con.c2c_dev, "Set Min latency\n"); + if (exynos_c2c_request_pwr_mode != NULL) + exynos_c2c_request_pwr_mode(MIN_LATENCY); + break; + case 1: + dev_info(c2c_con.c2c_dev, "Set Short latency\n"); + if (exynos_c2c_request_pwr_mode != NULL) + exynos_c2c_request_pwr_mode(SHORT_LATENCY); + break; + case 0: + dev_info(c2c_con.c2c_dev, "Set Max latency\n"); + if (exynos_c2c_request_pwr_mode != NULL) + exynos_c2c_request_pwr_mode(MAX_LATENCY); + break; + } + /* Interrupt Clear */ + c2c_writel((0x1 << C2C_GENIO_LATENCY_INT), EXYNOS_C2C_IRQ_EN_STAT1); + } + + return IRQ_HANDLED; +} + +static void set_c2c_device(struct platform_device *pdev) +{ + struct exynos_c2c_platdata *pdata = pdev->dev.platform_data; + u32 default_clk; + + c2c_con.c2c_sysreg = pdata->c2c_sysreg; + c2c_con.rx_width = pdata->rx_width; + c2c_con.tx_width = pdata->tx_width; + c2c_con.clk_opp100 = pdata->clk_opp100; + c2c_con.clk_opp50 = pdata->clk_opp50; + c2c_con.clk_opp25 = pdata->clk_opp25; + c2c_con.opp_mode = pdata->default_opp_mode; +#ifdef CONFIG_C2C_IPC_ENABLE + c2c_con.shd_pages = NULL; + c2c_con.hd.data = NULL; + c2c_con.hd.handler = NULL; +#endif + c2c_con.c2c_sclk = clk_get(&pdev->dev, "sclk_c2c"); + c2c_con.c2c_aclk = clk_get(&pdev->dev, "aclk_c2c"); + + if (soc_is_exynos4212()) + exynos_c2c_request_pwr_mode = exynos4_c2c_request_pwr_mode; + else if (soc_is_exynos4412()) { + exynos_c2c_request_pwr_mode = exynos4_c2c_request_pwr_mode; + if (samsung_rev() >= EXYNOS4412_REV_1_0) + writel(C2C_SYSREG_DEFAULT, c2c_con.c2c_sysreg); + } else if (soc_is_exynos5250()) + exynos_c2c_request_pwr_mode = NULL; + + /* Set clock to default mode */ + if (c2c_con.opp_mode == C2C_OPP100) + default_clk = c2c_con.clk_opp100; + else if (c2c_con.opp_mode == C2C_OPP50) + default_clk = c2c_con.clk_opp50; + else if (c2c_con.opp_mode == C2C_OPP25) + default_clk = c2c_con.clk_opp25; + else { + dev_info(c2c_con.c2c_dev, "Default OPP mode is not selected.\n"); + c2c_con.opp_mode = C2C_OPP50; + default_clk = c2c_con.clk_opp50; + } + + clk_set_rate(c2c_con.c2c_sclk, (default_clk + 1) * MHZ); + clk_set_rate(c2c_con.c2c_aclk, ((default_clk / 2) + 1) * MHZ); + + dev_info(c2c_con.c2c_dev, "Get C2C sclk rate : %ld\n", + clk_get_rate(c2c_con.c2c_sclk)); + dev_info(c2c_con.c2c_dev, "Get C2C aclk rate : %ld\n", + clk_get_rate(c2c_con.c2c_aclk)); + if (pdata->setup_gpio) + pdata->setup_gpio(pdata->rx_width, pdata->tx_width); + + c2c_set_sharedmem(pdata->shdmem_size, pdata->shdmem_addr); + + /* Set SYSREG to memdone */ + c2c_set_memdone(C2C_SET); + c2c_set_clock_gating(C2C_CLEAR); + + /* Set C2C clock register to OPP50 */ + c2c_writel(default_clk, EXYNOS_C2C_FCLK_FREQ); + c2c_writel(default_clk, EXYNOS_C2C_RX_MAX_FREQ); + c2c_set_func_clk(default_clk); + + /* Set C2C buswidth */ + c2c_writel(((pdata->rx_width << 4) | (pdata->tx_width)), + EXYNOS_C2C_PORTCONFIG); + c2c_set_tx_buswidth(pdata->tx_width); + c2c_set_rx_buswidth(pdata->rx_width); + + /* Enable all of GENI/O Interrupt */ + c2c_writel((0x1 << C2C_GENIO_OPP_INT), EXYNOS_C2C_IRQ_EN_SET1); + c2c_con.retention_reg = (0x1 << C2C_GENIO_OPP_INT); + + if (exynos_c2c_request_pwr_mode != NULL) + exynos_c2c_request_pwr_mode(MAX_LATENCY); + + c2c_set_interrupt(C2C_GENIO_OPP_INT, C2C_INT_HIGH); + + dev_info(c2c_con.c2c_dev, "Port Config : 0x%x\n", + c2c_readl(EXYNOS_C2C_PORTCONFIG)); + dev_info(c2c_con.c2c_dev, "FCLK_FREQ register : %d\n", + c2c_readl(EXYNOS_C2C_FCLK_FREQ)); + dev_info(c2c_con.c2c_dev, "RX_MAX_FREQ register : %d\n", + c2c_readl(EXYNOS_C2C_RX_MAX_FREQ)); + dev_info(c2c_con.c2c_dev, "IRQ_EN_SET1 register : 0x%x\n", + c2c_readl(EXYNOS_C2C_IRQ_EN_SET1)); + + c2c_set_clock_gating(C2C_SET); +} + +#ifdef CONFIG_C2C_IPC_ENABLE +void __iomem *c2c_request_cp_region(unsigned int cp_addr, + unsigned int size) +{ + dma_addr_t phy_cpmem; + + phy_cpmem = cma_alloc(c2c_con.c2c_dev, "c2c_shdmem", size, 0); + if (IS_ERR_VALUE(phy_cpmem)) { + dev_info(c2c_con.c2c_dev, KERN_ERR "C2C CMA Alloc Error!!!"); + return NULL; + } + + return phys_to_virt(phy_cpmem); +} +EXPORT_SYMBOL(c2c_request_cp_region); + +void c2c_release_cp_region(void *rgn) +{ + dma_addr_t phy_cpmem; + + phy_cpmem = virt_to_phys(rgn); + + cma_free(phy_cpmem); +} +EXPORT_SYMBOL(c2c_release_cp_region); + +void __iomem *c2c_request_sh_region(unsigned int sh_addr, + unsigned int size) +{ + int i; + struct page **pages; + void *pv; + + pages = kmalloc((size >> PAGE_SHIFT) * sizeof(*pages), GFP_KERNEL); + for (i = 0; i < (size >> PAGE_SHIFT); i++) { + pages[i] = phys_to_page(sh_addr); + sh_addr += PAGE_SIZE; + } + + c2c_con.shd_pages = (void *)pages; + + pv = vmap(pages, size >> PAGE_SHIFT, VM_MAP, + pgprot_noncached(PAGE_KERNEL)); + + return (void __iomem *)pv; +} +EXPORT_SYMBOL(c2c_request_sh_region); + +void c2c_release_sh_region(void *rgn) +{ + vunmap(rgn); + kfree(c2c_con.shd_pages); + c2c_con.shd_pages = NULL; +} +EXPORT_SYMBOL(c2c_release_sh_region); + +int c2c_register_handler(void (*handler)(void *), void *data) +{ + if (!handler) + return -EINVAL; + + c2c_con.hd.data = data; + c2c_con.hd.handler = handler; + + c2c_reset_interrupt(); + + return 0; +} +EXPORT_SYMBOL(c2c_register_handler); + +int c2c_unregister_handler(void (*handler)(void *)) +{ + if (!handler || (c2c_con.hd.handler != handler)) + return -EINVAL; + + c2c_con.hd.data = NULL; + c2c_con.hd.handler = NULL; + return 0; +} +EXPORT_SYMBOL(c2c_unregister_handler); + +void c2c_send_interrupt(void) +{ + c2c_writel(c2c_readl(EXYNOS_C2C_GENI_CONTROL) ^ 0x1, + EXYNOS_C2C_GENI_CONTROL); +} +EXPORT_SYMBOL(c2c_send_interrupt); + +void c2c_reset_interrupt(void) +{ + c2c_writel(c2c_readl(EXYNOS_C2C_IRQ_EN_SET1) | 0x1, + EXYNOS_C2C_IRQ_EN_SET1); + c2c_con.retention_reg |= 0x1; +} +EXPORT_SYMBOL(c2c_reset_interrupt); +#endif + +static int __devinit samsung_c2c_probe(struct platform_device *pdev) +{ + struct exynos_c2c_platdata *pdata = pdev->dev.platform_data; + struct resource *res = NULL; + struct resource *res1 = NULL; + int sscm_irq0, sscm_irq1; + int err = 0; + + c2c_con.c2c_dev = &pdev->dev; + + /* resource for AP's SSCM region */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no memory resource defined(AP's SSCM)\n"); + return -ENOENT; + } + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (!res) { + dev_err(&pdev->dev, "failded to request memory resource(AP)\n"); + return -ENOENT; + } + pdata->ap_sscm_addr = ioremap(res->start, resource_size(res)); + if (!pdata->ap_sscm_addr) { + dev_err(&pdev->dev, "failded to request memory resource(AP)\n"); + goto release_ap_sscm; + } + c2c_con.ap_sscm_addr = pdata->ap_sscm_addr; + + /* resource for CP's SSCM region */ + res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res1) { + dev_err(&pdev->dev, "no memory resource defined(AP's SSCM)\n"); + goto unmap_ap_sscm; + } + res1 = request_mem_region(res1->start, resource_size(res1), pdev->name); + if (!res1) { + dev_err(&pdev->dev, "failded to request memory resource(AP)\n"); + goto unmap_ap_sscm; + } + pdata->cp_sscm_addr = ioremap(res1->start, resource_size(res1)); + if (!pdata->cp_sscm_addr) { + dev_err(&pdev->dev, "failded to request memory resource(CP)\n"); + goto release_cp_sscm; + } + c2c_con.cp_sscm_addr = pdata->cp_sscm_addr; + + /* Request IRQ */ + sscm_irq0 = platform_get_irq(pdev, 0); + if (sscm_irq0 < 0) { + dev_err(&pdev->dev, "no irq specified\n"); + goto unmap_cp_sscm; + } + err = request_irq(sscm_irq0, c2c_sscm0_irq, 0, pdev->name, pdev); + if (err) { + dev_err(&pdev->dev, "Can't request SSCM0 IRQ\n"); + goto unmap_cp_sscm; + } + /* SSCM0 irq will be only used for master(CP) device */ + disable_irq(sscm_irq0); + + sscm_irq1 = platform_get_irq(pdev, 1); + if (sscm_irq1 < 0) { + dev_err(&pdev->dev, "no irq specified\n"); + goto release_sscm_irq0; + } + err = request_irq(sscm_irq1, c2c_sscm1_irq, 1, pdev->name, pdev); + if (err) { + dev_err(&pdev->dev, "Can't request SSCM1 IRQ\n"); + goto release_sscm_irq0; + } + + err = misc_register(&char_dev); + if (err) { + dev_err(&pdev->dev, "Can't register chrdev!\n"); + goto release_sscm_irq0; + } + + set_c2c_device(pdev); + +#ifdef ENABLE_C2CSTATE_TIMER + /* Timer for debugging to check C2C state */ + init_timer(&c2c_status_timer); + c2c_status_timer.expires = jiffies + HZ; + c2c_status_timer.data = (unsigned long)pdata; + c2c_status_timer.function = &c2c_timer_func; + add_timer(&c2c_status_timer); +#endif + + /* Create sysfs file for C2C debug */ + err = device_create_file(&pdev->dev, &dev_attr_c2c_ctrl); + if (err) { + dev_err(&pdev->dev, "Failed to create sysfs for C2C\n"); + goto release_sscm_irq1; + } + + return 0; + +release_sscm_irq1: + free_irq(sscm_irq1, pdev); + +release_sscm_irq0: + free_irq(sscm_irq0, pdev); + +unmap_cp_sscm: + iounmap(pdata->cp_sscm_addr); + +release_cp_sscm: + release_mem_region(res1->start, resource_size(res1)); + +unmap_ap_sscm: + iounmap(pdata->ap_sscm_addr); + +release_ap_sscm: + release_mem_region(res->start, resource_size(res)); + + return err; +} + +static int __devexit samsung_c2c_remove(struct platform_device *pdev) +{ + /* TODO */ + return 0; +} + +#ifdef CONFIG_PM +static int samsung_c2c_suspend(struct platform_device *dev, pm_message_t pm) +{ + /* TODO */ + return 0; +} + +static int samsung_c2c_resume(struct platform_device *dev) +{ + struct exynos_c2c_platdata *pdata = dev->dev.platform_data; + + if ((soc_is_exynos4212() || soc_is_exynos4412()) + && samsung_rev() == EXYNOS4412_REV_0) { + /* Set SYSREG */ + c2c_set_sharedmem(pdata->shdmem_size, pdata->shdmem_addr); + c2c_set_memdone(C2C_SET); + } else if (soc_is_exynos5250()) { + /* Set SYSREG */ + c2c_set_sharedmem(pdata->shdmem_size, pdata->shdmem_addr); + c2c_set_memdone(C2C_SET); + } + + return 0; +} +#else +#define samsung_c2c_suspend NULL +#define samsung_c2c_resume NULL +#endif + +static struct platform_driver samsung_c2c_driver = { + .probe = samsung_c2c_probe, + .remove = __devexit_p(samsung_c2c_remove), + .suspend = samsung_c2c_suspend, + .resume = samsung_c2c_resume, + .driver = { + .name = "samsung-c2c", + .owner = THIS_MODULE, + }, +}; + +static int __init samsung_c2c_init(void) +{ + return platform_driver_register(&samsung_c2c_driver); +} +module_init(samsung_c2c_init); + +static void __exit samsung_c2c_exit(void) +{ + platform_driver_unregister(&samsung_c2c_driver); +} +module_exit(samsung_c2c_exit); + +MODULE_DESCRIPTION("Samsung C2C driver"); +MODULE_AUTHOR("Kisang Lee <kisang80.lee@samsung.com>"); +MODULE_LICENSE("GPL"); |