diff options
Diffstat (limited to 'drivers/misc/stm.c')
-rw-r--r-- | drivers/misc/stm.c | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/drivers/misc/stm.c b/drivers/misc/stm.c new file mode 100644 index 00000000000..3782d837291 --- /dev/null +++ b/drivers/misc/stm.c @@ -0,0 +1,414 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/miscdevice.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <mach/prcmu-regs.h> +#include <trace/stm.h> + +#define STM_CLOCK_SHIFT 6 +#define STM_CLOCK_MASK 0x1C0 +#define STM_ENABLE_MASK 0x23D +/* Software mode for all cores except PRCMU that doesn't support SW */ +#define STM_MMC_DEFAULT 0x20 + +/* STM Registers */ +#define STM_CR (stm.virtbase) +#define STM_MMC (stm.virtbase + 0x008) +#define STM_TER (stm.virtbase + 0x010) +#define STMPERIPHID0 (stm.virtbase + 0xFC0) +#define STMPERIPHID1 (stm.virtbase + 0xFC8) +#define STMPERIPHID2 (stm.virtbase + 0xFD0) +#define STMPERIPHID3 (stm.virtbase + 0xFD8) +#define STMPCELLID0 (stm.virtbase + 0xFE0) +#define STMPCELLID1 (stm.virtbase + 0xFE8) +#define STMPCELLID2 (stm.virtbase + 0xFF0) +#define STMPCELLID3 (stm.virtbase + 0xFF8) + +static struct stm_device { + struct stm_platform_data *pdata; + void __iomem *virtbase; + volatile struct stm_channel __iomem *channels; + /* Used to register the allocated channels */ + DECLARE_BITMAP(ch_bitmap, STM_NUMBER_OF_CHANNEL); +} stm; + +static DEFINE_MUTEX(lock); + +static char *mipi60; +module_param(mipi60, charp, S_IRUGO); +MODULE_PARM_DESC(mipi60, "Trace to output on probe2 of mipi60 " + "('ape' or 'none')"); + +static char *mipi34 = "modem"; +module_param(mipi34, charp, S_IRUGO); +MODULE_PARM_DESC(mipi34, "Trace to output on mipi34 ('ape' or 'modem')"); + +#define IS_APE_ON_MIPI34 (mipi34 && !strcmp(mipi34, "ape")) +#define IS_APE_ON_MIPI60 (mipi60 && !strcmp(mipi60, "ape")) + +static int stm_open(struct inode *inode, struct file *file) +{ + file->private_data = kzalloc(sizeof(stm.ch_bitmap), GFP_KERNEL); + if (file->private_data == NULL) + return -ENOMEM; + return 0; +} + +static int stm_release(struct inode *inode, struct file *filp) +{ + bitmap_andnot(stm.ch_bitmap, stm.ch_bitmap, filp->private_data, + STM_NUMBER_OF_CHANNEL); + kfree(filp->private_data); + return 0; +} + +static int stm_mmap(struct file *filp, struct vm_area_struct *vma) +{ + /* + * Don't allow a mapping that covers more than the STM channels + * 4096 == sizeof(struct stm_channels) + */ + if ((vma->vm_end - vma->vm_start) > SZ_4K) + return -EINVAL; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (io_remap_pfn_range(vma, vma->vm_start, + stm.pdata->channels_phys_base>>PAGE_SHIFT, + SZ_4K, vma->vm_page_prot)) + return -EAGAIN ; + + return 0; +} + +void stm_disable_src(void) +{ + mutex_lock(&lock); + writel(0x0, STM_CR); /* stop clock */ + writel(STM_MMC_DEFAULT, STM_MMC); + writel(0x0, STM_TER); /* Disable cores */ + mutex_unlock(&lock); +} +EXPORT_SYMBOL(stm_disable_src); + +int stm_set_ckdiv(enum clock_div v) +{ + unsigned int val; + + mutex_lock(&lock); + val = readl(STM_CR); + val &= ~STM_CLOCK_MASK; + writel(val | ((v << STM_CLOCK_SHIFT) & STM_CLOCK_MASK), STM_CR); + mutex_unlock(&lock); + + return 0; +} +EXPORT_SYMBOL(stm_set_ckdiv); + +unsigned int stm_get_cr(void) +{ + return readl(STM_CR); +} +EXPORT_SYMBOL(stm_get_cr); + +int stm_enable_src(unsigned int v) +{ + unsigned int val; + mutex_lock(&lock); + val = readl(STM_CR); + val &= ~STM_CLOCK_MASK; + /* middle possible clock */ + writel(val & (STM_CLOCK_DIV8 << STM_CLOCK_SHIFT), STM_CR); + writel(STM_MMC_DEFAULT, STM_MMC); + writel((v & STM_ENABLE_MASK), STM_TER); + mutex_unlock(&lock); + return 0; +} +EXPORT_SYMBOL(stm_enable_src); + +static int stm_get_channel(struct file *filp, int __user *arg) +{ + int c, err; + + /* Look for a free channel */ + do { + c = find_first_zero_bit(stm.ch_bitmap, STM_NUMBER_OF_CHANNEL); + } while ((c < STM_NUMBER_OF_CHANNEL) + && test_and_set_bit(c, stm.ch_bitmap)); + + if (c < STM_NUMBER_OF_CHANNEL) { + /* One free found ! */ + err = put_user(c, arg); + if (err) { + clear_bit(c, stm.ch_bitmap); + } else { + /* Register it in the context of the file */ + unsigned long *local_bitmap = filp->private_data; + if (local_bitmap) + set_bit(c, local_bitmap); + } + } else { + err = -ENOMEM; + } + return err; +} + +static int stm_free_channel(struct file *filp, int channel) +{ + if ((channel < 0) || (channel >= STM_NUMBER_OF_CHANNEL)) + return -EINVAL; + clear_bit(channel, stm.ch_bitmap); + if (filp->private_data) + clear_bit(channel, filp->private_data); + return 0; +} + +static long stm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int err = 0; + + switch (cmd) { + + case STM_DISABLE: + stm_disable_src(); + break; + + case STM_SET_CLOCK_DIV: + err = stm_set_ckdiv((enum clock_div) arg); + break; + + case STM_GET_CTRL_REG: + err = put_user(stm_get_cr(), (unsigned int *)arg); + break; + + case STM_ENABLE_SRC: + err = stm_enable_src(arg); + break; + + case STM_DISABLE_MIPI34_MODEM: + stm.pdata->ste_disable_modem_on_mipi34(); + break; + + case STM_ENABLE_MIPI34_MODEM: + stm.pdata->ste_enable_modem_on_mipi34(); + break; + + case STM_GET_FREE_CHANNEL: + err = stm_get_channel(filp, (int *)arg); + break; + + case STM_RELEASE_CHANNEL: + err = stm_free_channel(filp, arg); + break; + + default: + err = -EINVAL; + break; + } + + return err; +} + +#define DEFLLTFUN(size) \ + void stm_trace_##size(unsigned char channel, uint##size##_t data) \ + { \ + (__chk_io_ptr(&(stm.channels[channel].no_stamp##size))), \ + *(volatile uint##size##_t __force *) \ + (&(stm.channels[channel].no_stamp##size)) = data;\ + } \ + EXPORT_SYMBOL(stm_trace_##size); \ + void stm_tracet_##size(unsigned char channel, uint##size##_t data) \ + { \ + (__chk_io_ptr(&(stm.channels[channel].stamp##size))), \ + *(volatile uint##size##_t __force *) \ + (&(stm.channels[channel].stamp##size)) = data; } \ + EXPORT_SYMBOL(stm_tracet_##size) + +DEFLLTFUN(8); +DEFLLTFUN(16); +DEFLLTFUN(32); +DEFLLTFUN(64); + +static const struct file_operations stm_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = stm_ioctl, + .open = stm_open, + .llseek = no_llseek, + .release = stm_release, + .mmap = stm_mmap, +}; + +static struct miscdevice stm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = STM_DEV_NAME, + .fops = &stm_fops +}; + +static int __devinit stm_probe(struct platform_device *pdev) +{ + int err; + + if (!pdev || !pdev->dev.platform_data) { + pr_alert("No device/platform_data found on STM driver\n"); + return -ENODEV; + } + + stm.pdata = pdev->dev.platform_data; + + /* Reserve channels if necessary */ + if (stm.pdata->channels_reserved) { + int i = 0; + while (stm.pdata->channels_reserved[i] != -1) { + set_bit(stm.pdata->channels_reserved[i], stm.ch_bitmap); + i++; + } + } + + err = misc_register(&stm_misc); + if (err) { + dev_alert(&pdev->dev, "Unable to register misc driver!\n"); + return err; + } + + stm.virtbase = ioremap_nocache(stm.pdata->regs_phys_base, SZ_4K); + if (stm.virtbase == NULL) { + err = -EIO; + dev_err(&pdev->dev, "could not remap STM Register\n"); + goto fail_init; + } + + stm.channels = + ioremap_nocache(stm.pdata->channels_phys_base, + STM_NUMBER_OF_CHANNEL*sizeof(*stm.channels)); + if (stm.channels == NULL) { + dev_err(&pdev->dev, "could not remap STM Msg register\n"); + goto fail_init; + } + + /* Check chip IDs if necessary */ + if (stm.pdata->periph_id && stm.pdata->cell_id) { + u32 periph_id, cell_id; + + periph_id = (readb(STMPERIPHID0)<<24) + + (readb(STMPERIPHID1)<<16) + + (readb(STMPERIPHID2)<<8) + + readb(STMPERIPHID3); + cell_id = (readb(STMPCELLID0)<<24) + + (readb(STMPCELLID1)<<16) + + (readb(STMPCELLID2)<<8) + + readb(STMPCELLID3); + /* Ignore periph id2 field verification */ + if ((stm.pdata->periph_id & 0xFFFF00FF) + != (periph_id & 0xFFFF00FF) || + stm.pdata->cell_id != cell_id) { + dev_err(&pdev->dev, "STM-Trace not supported\n"); + dev_err(&pdev->dev, "periph_id=%x\n", periph_id); + dev_err(&pdev->dev, "pcell_id=%x\n", cell_id); + err = -EINVAL; + goto fail_init; + } + } + + if (IS_APE_ON_MIPI60) { + if (IS_APE_ON_MIPI34) { + dev_info(&pdev->dev, "Can't not enable APE trace on " + "mipi34 and mipi60-probe2: disabling APE on" + " mipi34\n"); + mipi34 = "modem"; + } + if (stm.pdata->ste_gpio_enable_ape_modem_mipi60) { + err = stm.pdata->ste_gpio_enable_ape_modem_mipi60(); + if (err) + dev_err(&pdev->dev, "can't enable MIPI60\n"); + } + } + + if (IS_APE_ON_MIPI34) { + if (stm.pdata->ste_disable_modem_on_mipi34) + stm.pdata->ste_disable_modem_on_mipi34(); + } else { + if (stm.pdata->ste_enable_modem_on_mipi34) + stm.pdata->ste_enable_modem_on_mipi34(); + } + + if (stm.pdata->ste_gpio_enable_mipi34) { + err = stm.pdata->ste_gpio_enable_mipi34(); + if (err) { + dev_err(&pdev->dev, "failed to set GPIO_ALT_TRACE\n"); + goto fail_init; + } + } + + if (stm.pdata->masters_enabled) + stm_enable_src(stm.pdata->masters_enabled); + dev_info(&pdev->dev, "STM-Trace driver probed successfully\n"); + return 0; +fail_init: + if (stm.virtbase) + iounmap(stm.virtbase); + + if (stm.channels) + iounmap(stm.channels); + misc_deregister(&stm_misc); + + return err; +} + +static int __devexit stm_remove(struct platform_device *pdev) +{ + struct stm_platform_data *pdata; + pdata = pdev->dev.platform_data; + + if (pdata->ste_gpio_disable_mipi34) + pdata->ste_gpio_disable_mipi34(); + + stm_disable_src(); + + if (stm.virtbase) + iounmap(stm.virtbase); + + if (stm.channels) + iounmap(stm.channels); + + misc_deregister(&stm_misc); + return 0; +} + +static struct platform_driver stm_driver = { + .probe = stm_probe, + .remove = __devexit_p(stm_remove), + .driver = { + .name = "stm", + .owner = THIS_MODULE, + } +}; + +static int __init stm_init(void) +{ + return platform_driver_register(&stm_driver); +} + +static void __exit stm_exit(void) +{ + platform_driver_unregister(&stm_driver); +} + +module_init(stm_init); +module_exit(stm_exit); + +MODULE_AUTHOR("Paul Ghaleb - ST Microelectronics"); +MODULE_AUTHOR("Pierre Peiffer - ST-Ericsson"); +MODULE_DESCRIPTION("Ux500 System Trace Module driver"); +MODULE_ALIAS("stm"); +MODULE_ALIAS("stm-trace"); +MODULE_LICENSE("GPL v2"); |