/* * Copyright (C) 2014 Samsung Electronics Co.Ltd * http://www.samsung.com * * MCU IPC driver * * 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 #include #include #include #include #include #include #include #include #include #include "regs-mcu_ipc.h" #include "mcu_ipc.h" #define USE_FIXED_AFFINITY void mcu_ipc_reg_dump(enum mcu_ipc_region id) { unsigned long flags; u32 i, value; if (id >= MCU_MAX) return; spin_lock_irqsave(&mcu_dat[id].reg_lock, flags); for (i = 0; i < 4; i++) { value = mcu_ipc_readl(id, EXYNOS_MCU_IPC_ISSR0 + (4 * i)); dev_err(mcu_dat[id].mcu_ipc_dev, "mbox dump: 0x%02x: 0x%04x\n", i, value); } spin_unlock_irqrestore(&mcu_dat[id].reg_lock, flags); } EXPORT_SYMBOL(mcu_ipc_reg_dump); static irqreturn_t mcu_ipc_handler(int irq, void *data) { u32 irq_stat, i; u32 id; id = ((struct mcu_ipc_drv_data *)data)->id; spin_lock(&mcu_dat[id].reg_lock); /* Check raised interrupts */ irq_stat = mcu_ipc_readl(id, EXYNOS_MCU_IPC_INTSR0) & 0xFFFF0000; /* Only clear and handle unmasked interrupts */ irq_stat &= mcu_dat[id].unmasked_irq << 16; /* Interrupt Clear */ mcu_ipc_writel(id, irq_stat, EXYNOS_MCU_IPC_INTCR0); spin_unlock(&mcu_dat[id].reg_lock); for (i = 0; i < 16; i++) { if (irq_stat & (1 << (i + 16))) { if ((1 << (i + 16)) & mcu_dat[id].registered_irq) mcu_dat[id].hd[i].handler(mcu_dat[id].hd[i].data); else dev_err(mcu_dat[id].mcu_ipc_dev, "Unregistered INT received.\n"); irq_stat &= ~(1 << (i + 16)); } if (!irq_stat) break; } return IRQ_HANDLED; } int mbox_request_irq(enum mcu_ipc_region id, u32 int_num, void (*handler)(void *), void *data) { unsigned long flags; if ((!handler) || (int_num > 15)) return -EINVAL; spin_lock_irqsave(&mcu_dat[id].reg_lock, flags); mcu_dat[id].hd[int_num].data = data; mcu_dat[id].hd[int_num].handler = handler; mcu_dat[id].registered_irq |= 1 << (int_num + 16); set_bit(int_num, &mcu_dat[id].unmasked_irq); spin_unlock_irqrestore(&mcu_dat[id].reg_lock, flags); return 0; } EXPORT_SYMBOL(mbox_request_irq); /* * mbox_enable_irq * * This function unmasks a single mailbox interrupt. */ int mbox_enable_irq(enum mcu_ipc_region id, u32 int_num) { unsigned long flags; unsigned long tmp; /* The irq should have been registered. */ if (!(mcu_dat[id].registered_irq & BIT(int_num + 16))) return -EINVAL; spin_lock_irqsave(&mcu_dat[id].reg_lock, flags); tmp = mcu_ipc_readl(id, EXYNOS_MCU_IPC_INTMR0); /* Clear the mask if it was set. */ if (test_and_clear_bit(int_num + 16, &tmp)) mcu_ipc_writel(id, tmp, EXYNOS_MCU_IPC_INTMR0); /* Mark the irq as unmasked */ set_bit(int_num, &mcu_dat[id].unmasked_irq); spin_unlock_irqrestore(&mcu_dat[id].reg_lock, flags); return 0; } EXPORT_SYMBOL(mbox_enable_irq); /* * mbox_check_irq * * This function is used to check the state of the mailbox interrupt * when the interrupt after the interrupt has been masked. This can be * used to check if a new interrupt has been set after being masked. A * masked interrupt will have its status set but will not generate a hard * interrupt. This function will check and clear the status. */ int mbox_check_irq(enum mcu_ipc_region id, u32 int_num) { unsigned long flags; u32 irq_stat; /* Interrupt must have been registered. */ if (!(mcu_dat[id].registered_irq & BIT(int_num + 16))) return -EINVAL; spin_lock_irqsave(&mcu_dat[id].reg_lock, flags); /* Interrupt must have been masked. */ if (test_bit(int_num, &mcu_dat[id].unmasked_irq)) { spin_unlock_irqrestore(&mcu_dat[id].reg_lock, flags); return -EINVAL; } /* Check and clear the interrupt status bit. */ irq_stat = mcu_ipc_readl(id, EXYNOS_MCU_IPC_INTSR0) & BIT(int_num + 16); if (irq_stat) mcu_ipc_writel(id, irq_stat, EXYNOS_MCU_IPC_INTCR0); spin_unlock_irqrestore(&mcu_dat[id].reg_lock, flags); return irq_stat != 0; } EXPORT_SYMBOL(mbox_check_irq); /* * mbox_disable_irq * * This function masks and single mailbox interrupt. */ int mbox_disable_irq(enum mcu_ipc_region id, u32 int_num) { unsigned long flags; unsigned long irq_mask; /* The interrupt must have been registered. */ if (!(mcu_dat[id].registered_irq & BIT(int_num + 16))) return -EINVAL; /* Set the mask */ spin_lock_irqsave(&mcu_dat[id].reg_lock, flags); irq_mask = mcu_ipc_readl(id, EXYNOS_MCU_IPC_INTMR0); /* Set the mask if it was not already set */ if (!test_and_set_bit(int_num + 16, &irq_mask)) { mcu_ipc_writel(id, irq_mask, EXYNOS_MCU_IPC_INTMR0); udelay(5); /* Reset the status bit to signal interrupt needs handling */ mcu_ipc_writel(id, BIT(int_num + 16), EXYNOS_MCU_IPC_INTGR0); udelay(5); } /* Remove the irq from the umasked irqs */ clear_bit(int_num, &mcu_dat[id].unmasked_irq); spin_unlock_irqrestore(&mcu_dat[id].reg_lock, flags); return 0; } EXPORT_SYMBOL(mbox_disable_irq); int mcu_ipc_unregister_handler(enum mcu_ipc_region id, u32 int_num, void (*handler)(void *)) { unsigned long flags; if (!handler || (mcu_dat[id].hd[int_num].handler != handler)) return -EINVAL; spin_lock_irqsave(&mcu_dat[id].reg_lock, flags); mcu_dat[id].hd[int_num].data = NULL; mcu_dat[id].hd[int_num].handler = NULL; mcu_dat[id].registered_irq &= ~(1 << (int_num + 16)); clear_bit(int_num, &mcu_dat[id].unmasked_irq); spin_unlock_irqrestore(&mcu_dat[id].reg_lock, flags); return 0; } EXPORT_SYMBOL(mcu_ipc_unregister_handler); void mbox_set_interrupt(enum mcu_ipc_region id, u32 int_num) { /* generate interrupt */ if (int_num < 16) mcu_ipc_writel(id, 0x1 << int_num, EXYNOS_MCU_IPC_INTGR1); } EXPORT_SYMBOL(mbox_set_interrupt); void mcu_ipc_send_command(enum mcu_ipc_region id, u32 int_num, u16 cmd) { /* write command */ if (int_num < 16) mcu_ipc_writel(id, cmd, EXYNOS_MCU_IPC_ISSR0 + (8 * int_num)); /* generate interrupt */ mbox_set_interrupt(id, int_num); } EXPORT_SYMBOL(mcu_ipc_send_command); u32 mbox_get_value(enum mcu_ipc_region id, u32 mbx_num) { if (mbx_num < 64) return mcu_ipc_readl(id, EXYNOS_MCU_IPC_ISSR0 + (4 * mbx_num)); else return 0; } EXPORT_SYMBOL(mbox_get_value); void mbox_set_value(enum mcu_ipc_region id, u32 mbx_num, u32 msg) { if (mbx_num < 64) mcu_ipc_writel(id, msg, EXYNOS_MCU_IPC_ISSR0 + (4 * mbx_num)); } EXPORT_SYMBOL(mbox_set_value); u32 mbox_extract_value(enum mcu_ipc_region id, u32 mbx_num, u32 mask, u32 pos) { if (mbx_num < 64) return (mbox_get_value(id, mbx_num) >> pos) & mask; else return 0; } EXPORT_SYMBOL(mbox_extract_value); void mbox_update_value(enum mcu_ipc_region id, u32 mbx_num, u32 msg, u32 mask, u32 pos) { u32 val; unsigned long flags; spin_lock_irqsave(&mcu_dat[id].lock, flags); if (mbx_num < 64) { val = mbox_get_value(id, mbx_num); val &= ~(mask << pos); val |= (msg & mask) << pos; mbox_set_value(id, mbx_num, val); } spin_unlock_irqrestore(&mcu_dat[id].lock, flags); } EXPORT_SYMBOL(mbox_update_value); void mbox_sw_reset(enum mcu_ipc_region id) { u32 reg_val; printk("Reset Mailbox registers\n"); reg_val = mcu_ipc_readl(id, EXYNOS_MCU_IPC_MCUCTLR); reg_val |= (0x1 << MCU_IPC_MCUCTLR_MSWRST); mcu_ipc_writel(id, reg_val, EXYNOS_MCU_IPC_MCUCTLR) ; udelay(5); } EXPORT_SYMBOL(mbox_sw_reset); static void mcu_ipc_clear_all_interrupt(enum mcu_ipc_region id) { mcu_ipc_writel(id, 0xFFFF, EXYNOS_MCU_IPC_INTCR1); } #ifdef CONFIG_ARGOS static int set_irq_affinity(struct device *dev) { struct mcu_argos_info *argos_info = dev->driver_data; if (argos_info != NULL) { dev_dbg(dev, "set default irq affinity (0x%x)\n", argos_info->affinity); return irq_set_affinity(argos_info->irq, cpumask_of(argos_info->affinity)); } else { /* skip other mcu_ipc device that has no irq affinity : gnss, ... */ return 0; } } #ifdef USE_FIXED_AFFINITY static int set_fixed_affinity(struct device *dev, int irq, u32 mask) { struct mcu_argos_info *argos_info; argos_info = devm_kzalloc(dev, sizeof(struct mcu_argos_info), GFP_KERNEL); if (!argos_info) { dev_err(dev, "Failed to alloc argos info sturct\n"); return -ENOMEM; } argos_info->irq = irq; argos_info->affinity = mask; dev_set_drvdata(dev, argos_info); return set_irq_affinity(dev); } #else static int set_runtime_affinity(enum mcu_ipc_region id, int irq, u32 mask) { if (!zalloc_cpumask_var(&mcu_dat[id].dmask, GFP_KERNEL)) return -ENOMEM; if (!zalloc_cpumask_var(&mcu_dat[id].imask, GFP_KERNEL)) return -ENOMEM; cpumask_or(mcu_dat[id].imask, mcu_dat[id].imask, cpumask_of(mask)); cpumask_copy(mcu_dat[id].dmask, get_default_cpu_mask()); return argos_irq_affinity_setup_label(irq, "IPC", mcu_dat[id].imask, mcu_dat[id].dmask); } #endif static int mcu_ipc_set_affinity(enum mcu_ipc_region id, struct device *dev, int irq) { struct device_node *np = dev->of_node; u32 irq_affinity_mask = 0; if (!np) { dev_err(dev, "non-DT project, can't set irq affinity\n"); return -ENODEV; } if (of_property_read_u32(np, "mcu,irq_affinity_mask", &irq_affinity_mask)) { dev_err(dev, "Failed to get affinity mask\n"); return -ENODEV; } dev_info(dev, "irq_affinity_mask = 0x%x\n", irq_affinity_mask); #ifdef USE_FIXED_AFFINITY return set_fixed_affinity(dev, irq, irq_affinity_mask); #else return set_runtime_affinity(id, irq, irq_affinity_mask); #endif } #else static int mcu_ipc_set_affinity(enum mcu_ipc_region id, struct device *dev, int irq) { return 0; } #endif #ifdef CONFIG_MCU_IPC_TEST static void test_without_dev(enum mcu_ipc_region id) { int i; char *region; switch(id) { case MCU_CP: region = "CP"; break; case MCU_GNSS: region = "GNSS"; break; default: region = NULL; } for (i = 0; i < 64; i++) { mbox_set_value(id, i, 64 - i); mdelay(50); dev_err(mcu_dat[id].mcu_ipc_dev, "Test without %s(%d): Read mbox value[%d]: %d\n", region, id, i, mbox_get_value(i)); } } #endif static int mcu_ipc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *res = NULL; int mcu_ipc_irq; int err = 0; u32 id = 0; dev_err(&pdev->dev, "%s: mcu_ipc probe start.\n", __func__); err = of_property_read_u32(dev->of_node, "mcu,id", &id); if (err) { dev_err(&pdev->dev, "MCU IPC parse error! [id]\n"); return err; } if (id >= MCU_MAX) { dev_err(&pdev->dev, "MCU IPC Invalid ID [%d]\n", id); return -EINVAL; } mcu_dat[id].id = id; mcu_dat[id].mcu_ipc_dev = &pdev->dev; if (!pdev->dev.dma_mask) pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; if (!pdev->dev.coherent_dma_mask) pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); if (dev->of_node) { mcu_dt_read_string(dev->of_node, "mcu,name", mcu_dat[id].name); if (IS_ERR(&mcu_dat[id])) { dev_err(&pdev->dev, "MCU IPC parse error!\n"); return PTR_ERR(&mcu_dat[id]); } } /* resource for mcu_ipc SFR region */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); mcu_dat[id].ioaddr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(mcu_dat[id].ioaddr)) { dev_err(&pdev->dev, "failded to request memory resource\n"); return PTR_ERR(mcu_dat[id].ioaddr); } /* Request IRQ */ mcu_ipc_irq = platform_get_irq(pdev, 0); err = devm_request_irq(&pdev->dev, mcu_ipc_irq, mcu_ipc_handler, 0, pdev->name, &mcu_dat[id]); if (err) { dev_err(&pdev->dev, "Can't request MCU_IPC IRQ\n"); return err; } mcu_ipc_clear_all_interrupt(id); /* set argos irq affinity */ err = mcu_ipc_set_affinity(id, dev, mcu_ipc_irq); if (err) dev_err(dev, "Can't set IRQ affinity with(%d)\n", err); #ifdef CONFIG_MCU_IPC_TEST test_without_dev(id); #endif spin_lock_init(&mcu_dat[id].lock); spin_lock_init(&mcu_dat[id].reg_lock); dev_err(&pdev->dev, "%s: mcu_ipc probe done.\n", __func__); return 0; } static int __exit mcu_ipc_remove(struct platform_device *pdev) { /* TODO */ return 0; } #ifdef CONFIG_PM static int mcu_ipc_suspend(struct device *dev) { /* TODO */ return 0; } static int mcu_ipc_resume(struct device *dev) { /* TODO */ #ifdef CONFIG_ARGOS set_irq_affinity(dev); #endif return 0; } #else #define mcu_ipc_suspend NULL #define mcu_ipc_resume NULL #endif static const struct dev_pm_ops mcu_ipc_pm_ops = { .suspend = mcu_ipc_suspend, .resume = mcu_ipc_resume, }; static const struct of_device_id exynos_mcu_ipc_dt_match[] = { { .compatible = "samsung,exynos7580-mailbox", }, { .compatible = "samsung,exynos7890-mailbox", }, { .compatible = "samsung,exynos8890-mailbox", }, { .compatible = "samsung,exynos7870-mailbox", }, { .compatible = "samsung,exynos-shd-ipc-mailbox", }, {}, }; MODULE_DEVICE_TABLE(of, exynos_mcu_ipc_dt_match); static struct platform_driver mcu_ipc_driver = { .probe = mcu_ipc_probe, .remove = mcu_ipc_remove, .driver = { .name = "mcu_ipc", .owner = THIS_MODULE, .of_match_table = of_match_ptr(exynos_mcu_ipc_dt_match), .pm = &mcu_ipc_pm_ops, .suppress_bind_attrs = true, }, }; module_platform_driver(mcu_ipc_driver); MODULE_DESCRIPTION("MCU IPC driver"); MODULE_AUTHOR(""); MODULE_LICENSE("GPL");