| /* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| /* |
| * msm_dsps - control DSPS clocks, gpios and vregs. |
| * |
| */ |
| |
| #include <linux/types.h> |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/cdev.h> |
| #include <linux/fs.h> |
| #include <linux/platform_device.h> |
| #include <linux/err.h> |
| #include <linux/delay.h> |
| #include <linux/clk.h> |
| #include <linux/gpio.h> |
| #include <linux/string.h> |
| #include <linux/uaccess.h> |
| #include <linux/io.h> |
| #include <linux/msm_dsps.h> |
| |
| #include <mach/irqs.h> |
| #include <mach/msm_iomap.h> |
| #include <mach/msm_smsm.h> |
| #include <mach/msm_dsps.h> |
| #include <mach/subsystem_restart.h> |
| |
| #include "ramdump.h" |
| #include "timer.h" |
| |
| #define DRV_NAME "msm_dsps" |
| #define DRV_VERSION "4.03" |
| |
| |
| #define PPSS_TIMER0_32KHZ_REG 0x1004 |
| #define PPSS_TIMER0_20MHZ_REG 0x0804 |
| |
| /** |
| * Driver Context |
| * |
| * @dev_class - device class. |
| * @dev_num - device major & minor number. |
| * @dev - the device. |
| * @cdev - character device for user interface. |
| * @pdata - platform data. |
| * @pil - handle to DSPS Firmware loader. |
| * @dspsfw_ramdump_dev - handle to ramdump device for DSPS |
| * @dspsfw_ramdump_segments - Ramdump segment information for DSPS |
| * @smem_ramdump_dev - handle to ramdump device for smem |
| * @smem_ramdump_segments - Ramdump segment information for smem |
| * @is_on - DSPS is on. |
| * @ref_count - open/close reference count. |
| * @ppss_base - ppss registers virtual base address. |
| */ |
| struct dsps_drv { |
| |
| struct class *dev_class; |
| dev_t dev_num; |
| struct device *dev; |
| struct cdev *cdev; |
| |
| struct msm_dsps_platform_data *pdata; |
| |
| void *pil; |
| |
| int is_on; |
| int ref_count; |
| |
| void __iomem *ppss_base; |
| }; |
| |
| /** |
| * Driver context. |
| */ |
| static struct dsps_drv *drv; |
| |
| /** |
| * Load DSPS Firmware. |
| */ |
| static int dsps_load(void) |
| { |
| pr_debug("%s.\n", __func__); |
| |
| drv->pil = subsystem_get("dsps"); |
| if (IS_ERR(drv->pil)) { |
| pr_err("%s: fail to load DSPS firmware.\n", __func__); |
| return -ENODEV; |
| } |
| msleep(20); |
| return 0; |
| } |
| |
| /** |
| * Unload DSPS Firmware. |
| */ |
| static void dsps_unload(void) |
| { |
| pr_debug("%s.\n", __func__); |
| |
| subsystem_put(drv->pil); |
| } |
| |
| /** |
| * Suspend DSPS CPU. |
| * |
| * Only call if dsps_pwr_ctl_en is false. |
| * If dsps_pwr_ctl_en is true, then DSPS will control its own power state. |
| */ |
| static void dsps_suspend(void) |
| { |
| pr_debug("%s.\n", __func__); |
| |
| writel_relaxed(1, drv->ppss_base + drv->pdata->ppss_pause_reg); |
| mb(); /* Make sure write commited before ioctl returns. */ |
| } |
| |
| /** |
| * Resume DSPS CPU. |
| * |
| * Only call if dsps_pwr_ctl_en is false. |
| * If dsps_pwr_ctl_en is true, then DSPS will control its own power state. |
| */ |
| static void dsps_resume(void) |
| { |
| pr_debug("%s.\n", __func__); |
| |
| writel_relaxed(0, drv->ppss_base + drv->pdata->ppss_pause_reg); |
| mb(); /* Make sure write commited before ioctl returns. */ |
| } |
| |
| /** |
| * Read DSPS slow timer. |
| */ |
| static u32 dsps_read_slow_timer(void) |
| { |
| u32 val; |
| |
| /* Read the timer value from the MSM sclk. The MSM slow clock & DSPS |
| * timers are in sync, so these are the same value */ |
| val = msm_timer_get_sclk_ticks(); |
| pr_debug("%s.count=%d.\n", __func__, val); |
| |
| return val; |
| } |
| |
| /** |
| * Read DSPS fast timer. |
| */ |
| static u32 dsps_read_fast_timer(void) |
| { |
| u32 val; |
| |
| val = readl_relaxed(drv->ppss_base + PPSS_TIMER0_20MHZ_REG); |
| rmb(); /* order reads from the user output buffer */ |
| |
| pr_debug("%s.count=%d.\n", __func__, val); |
| |
| return val; |
| } |
| |
| /** |
| * Power on request. |
| * |
| * Set clocks to ON. |
| * Set sensors chip-select GPIO to non-reset (on) value. |
| * |
| */ |
| static int dsps_power_on_handler(void) |
| { |
| int ret = 0; |
| int i, ci, gi, ri; |
| |
| pr_debug("%s.\n", __func__); |
| |
| if (drv->is_on) { |
| pr_debug("%s: already ON.\n", __func__); |
| return 0; |
| } |
| |
| for (ci = 0; ci < drv->pdata->clks_num; ci++) { |
| const char *name = drv->pdata->clks[ci].name; |
| u32 rate = drv->pdata->clks[ci].rate; |
| struct clk *clock = drv->pdata->clks[ci].clock; |
| |
| if (clock == NULL) |
| continue; |
| |
| if (rate > 0) { |
| ret = clk_set_rate(clock, rate); |
| pr_debug("%s: clk %s set rate %d.", |
| __func__, name, rate); |
| if (ret) { |
| pr_err("%s: clk %s set rate %d. err=%d.", |
| __func__, name, rate, ret); |
| goto clk_err; |
| } |
| |
| } |
| |
| ret = clk_prepare_enable(clock); |
| if (ret) { |
| pr_err("%s: enable clk %s err %d.", |
| __func__, name, ret); |
| goto clk_err; |
| } |
| } |
| |
| for (gi = 0; gi < drv->pdata->gpios_num; gi++) { |
| const char *name = drv->pdata->gpios[gi].name; |
| int num = drv->pdata->gpios[gi].num; |
| int val = drv->pdata->gpios[gi].on_val; |
| int is_owner = drv->pdata->gpios[gi].is_owner; |
| |
| if (!is_owner) |
| continue; |
| |
| ret = gpio_direction_output(num, val); |
| if (ret) { |
| pr_err("%s: set GPIO %s num %d to %d err %d.", |
| __func__, name, num, val, ret); |
| goto gpio_err; |
| } |
| } |
| |
| for (ri = 0; ri < drv->pdata->regs_num; ri++) { |
| const char *name = drv->pdata->regs[ri].name; |
| struct regulator *reg = drv->pdata->regs[ri].reg; |
| int volt = drv->pdata->regs[ri].volt; |
| |
| if (reg == NULL) |
| continue; |
| |
| pr_debug("%s: set regulator %s.", __func__, name); |
| |
| ret = regulator_set_voltage(reg, volt, volt); |
| |
| if (ret) { |
| pr_err("%s: set regulator %s voltage %d err = %d.\n", |
| __func__, name, volt, ret); |
| goto reg_err; |
| } |
| |
| ret = regulator_enable(reg); |
| if (ret) { |
| pr_err("%s: enable regulator %s err = %d.\n", |
| __func__, name, ret); |
| goto reg_err; |
| } |
| } |
| |
| drv->is_on = true; |
| |
| return 0; |
| |
| /* |
| * If failling to set ANY clock/gpio/regulator to ON then we set |
| * them back to OFF to avoid consuming power for unused |
| * clocks/gpios/regulators. |
| */ |
| reg_err: |
| for (i = 0; i < ri; i++) { |
| struct regulator *reg = drv->pdata->regs[ri].reg; |
| |
| if (reg == NULL) |
| continue; |
| |
| regulator_disable(reg); |
| } |
| |
| gpio_err: |
| for (i = 0; i < gi; i++) { |
| int num = drv->pdata->gpios[i].num; |
| int val = drv->pdata->gpios[i].off_val; |
| int is_owner = drv->pdata->gpios[i].is_owner; |
| |
| if (!is_owner) |
| continue; |
| |
| ret = gpio_direction_output(num, val); |
| } |
| |
| clk_err: |
| for (i = 0; i < ci; i++) { |
| struct clk *clock = drv->pdata->clks[i].clock; |
| |
| if (clock == NULL) |
| continue; |
| |
| clk_disable_unprepare(clock); |
| } |
| |
| return -ENODEV; |
| } |
| |
| /** |
| * Power off request. |
| * |
| * Set clocks to OFF. |
| * Set sensors chip-select GPIO to reset (off) value. |
| * |
| */ |
| static int dsps_power_off_handler(void) |
| { |
| int ret; |
| int i; |
| |
| pr_debug("%s.\n", __func__); |
| |
| if (!drv->is_on) { |
| pr_debug("%s: already OFF.\n", __func__); |
| return 0; |
| } |
| |
| for (i = 0; i < drv->pdata->clks_num; i++) |
| if (drv->pdata->clks[i].clock) { |
| const char *name = drv->pdata->clks[i].name; |
| |
| pr_debug("%s: set clk %s off.", __func__, name); |
| clk_disable_unprepare(drv->pdata->clks[i].clock); |
| } |
| |
| for (i = 0; i < drv->pdata->regs_num; i++) |
| if (drv->pdata->regs[i].reg) { |
| const char *name = drv->pdata->regs[i].name; |
| |
| pr_debug("%s: set regulator %s off.", __func__, name); |
| regulator_disable(drv->pdata->regs[i].reg); |
| } |
| |
| /* Clocks on/off has reference count but GPIOs don't. */ |
| drv->is_on = false; |
| |
| for (i = 0; i < drv->pdata->gpios_num; i++) { |
| const char *name = drv->pdata->gpios[i].name; |
| int num = drv->pdata->gpios[i].num; |
| int val = drv->pdata->gpios[i].off_val; |
| |
| pr_debug("%s: set gpio %s off.", __func__, name); |
| |
| ret = gpio_direction_output(num, val); |
| if (ret) { |
| pr_err("%s: set GPIO %s err %d.", __func__, name, ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * IO Control - handle commands from client. |
| * |
| */ |
| static long dsps_ioctl(struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| int ret = 0; |
| u32 val = 0; |
| |
| pr_debug("%s.\n", __func__); |
| |
| switch (cmd) { |
| case DSPS_IOCTL_ON: |
| if (!drv->pdata->dsps_pwr_ctl_en) { |
| ret = dsps_power_on_handler(); |
| dsps_resume(); |
| } |
| break; |
| case DSPS_IOCTL_OFF: |
| if (!drv->pdata->dsps_pwr_ctl_en) { |
| dsps_suspend(); |
| ret = dsps_power_off_handler(); |
| } |
| break; |
| case DSPS_IOCTL_READ_SLOW_TIMER: |
| val = dsps_read_slow_timer(); |
| ret = put_user(val, (u32 __user *) arg); |
| break; |
| case DSPS_IOCTL_READ_FAST_TIMER: |
| val = dsps_read_fast_timer(); |
| ret = put_user(val, (u32 __user *) arg); |
| break; |
| case DSPS_IOCTL_RESET: |
| pr_err("%s: User-initiated DSPS reset.\nResetting DSPS\n", |
| __func__); |
| subsystem_restart("dsps"); |
| ret = 0; |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * allocate resources. |
| * @pdev - pointer to platform device. |
| */ |
| static int dsps_alloc_resources(struct platform_device *pdev) |
| { |
| int ret = -ENODEV; |
| struct resource *ppss_res; |
| int i; |
| |
| pr_debug("%s.\n", __func__); |
| |
| if ((drv->pdata->signature != DSPS_SIGNATURE)) { |
| pr_err("%s: invalid signature for pdata.", __func__); |
| return -EINVAL; |
| } |
| |
| ppss_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
| "ppss_reg"); |
| if (!ppss_res) { |
| pr_err("%s: failed to get ppss_reg resource.\n", __func__); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < drv->pdata->clks_num; i++) { |
| const char *name = drv->pdata->clks[i].name; |
| struct clk *clock; |
| |
| drv->pdata->clks[i].clock = NULL; |
| |
| pr_debug("%s: get clk %s.", __func__, name); |
| |
| clock = clk_get(drv->dev, name); |
| if (IS_ERR(clock)) { |
| pr_err("%s: can't get clk %s.", __func__, name); |
| goto clk_err; |
| } |
| drv->pdata->clks[i].clock = clock; |
| } |
| |
| for (i = 0; i < drv->pdata->gpios_num; i++) { |
| const char *name = drv->pdata->gpios[i].name; |
| int num = drv->pdata->gpios[i].num; |
| |
| drv->pdata->gpios[i].is_owner = false; |
| |
| pr_debug("%s: get gpio %s.", __func__, name); |
| |
| ret = gpio_request(num, name); |
| if (ret) { |
| pr_err("%s: request GPIO %s err %d.", |
| __func__, name, ret); |
| goto gpio_err; |
| } |
| |
| drv->pdata->gpios[i].is_owner = true; |
| |
| } |
| |
| for (i = 0; i < drv->pdata->regs_num; i++) { |
| const char *name = drv->pdata->regs[i].name; |
| |
| drv->pdata->regs[i].reg = NULL; |
| |
| pr_debug("%s: get regulator %s.", __func__, name); |
| |
| drv->pdata->regs[i].reg = regulator_get(drv->dev, name); |
| if (IS_ERR(drv->pdata->regs[i].reg)) { |
| pr_err("%s: get regulator %s failed.", |
| __func__, name); |
| goto reg_err; |
| } |
| } |
| |
| drv->ppss_base = ioremap(ppss_res->start, |
| resource_size(ppss_res)); |
| |
| if (drv->pdata->init) |
| drv->pdata->init(drv->pdata); |
| |
| return 0; |
| |
| reg_err: |
| for (i = 0; i < drv->pdata->regs_num; i++) { |
| if (drv->pdata->regs[i].reg) { |
| regulator_put(drv->pdata->regs[i].reg); |
| drv->pdata->regs[i].reg = NULL; |
| } |
| } |
| |
| gpio_err: |
| for (i = 0; i < drv->pdata->gpios_num; i++) |
| if (drv->pdata->gpios[i].is_owner) { |
| gpio_free(drv->pdata->gpios[i].num); |
| drv->pdata->gpios[i].is_owner = false; |
| } |
| clk_err: |
| for (i = 0; i < drv->pdata->clks_num; i++) |
| if (drv->pdata->clks[i].clock) { |
| clk_put(drv->pdata->clks[i].clock); |
| drv->pdata->clks[i].clock = NULL; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Open File. |
| * |
| */ |
| static int dsps_open(struct inode *ip, struct file *fp) |
| { |
| int ret = 0; |
| |
| pr_debug("%s.\n", __func__); |
| |
| if (drv->ref_count == 0) { |
| |
| /* clocks must be ON before loading.*/ |
| ret = dsps_power_on_handler(); |
| if (ret) |
| return ret; |
| |
| ret = dsps_load(); |
| |
| if (ret) { |
| dsps_power_off_handler(); |
| return ret; |
| } |
| |
| if (!drv->pdata->dsps_pwr_ctl_en) |
| dsps_resume(); |
| } |
| drv->ref_count++; |
| |
| return ret; |
| } |
| |
| /** |
| * free resources. |
| * |
| */ |
| static void dsps_free_resources(void) |
| { |
| int i; |
| |
| pr_debug("%s.\n", __func__); |
| |
| for (i = 0; i < drv->pdata->clks_num; i++) |
| if (drv->pdata->clks[i].clock) { |
| clk_put(drv->pdata->clks[i].clock); |
| drv->pdata->clks[i].clock = NULL; |
| } |
| |
| for (i = 0; i < drv->pdata->gpios_num; i++) |
| if (drv->pdata->gpios[i].is_owner) { |
| gpio_free(drv->pdata->gpios[i].num); |
| drv->pdata->gpios[i].is_owner = false; |
| } |
| |
| for (i = 0; i < drv->pdata->regs_num; i++) { |
| if (drv->pdata->regs[i].reg) { |
| regulator_put(drv->pdata->regs[i].reg); |
| drv->pdata->regs[i].reg = NULL; |
| } |
| } |
| |
| iounmap(drv->ppss_base); |
| } |
| |
| /** |
| * Close File. |
| * |
| * The client shall close and re-open the file for re-loading the DSPS |
| * firmware. |
| * The file system will close the file if the user space app has crashed. |
| * |
| * If the DSPS is running, then we must reset DSPS CPU & HW before |
| * setting the clocks off. |
| * The DSPS reset should be done as part of the pil_put(). |
| * The DSPS reset should be used for error recovery if the DSPS firmware |
| * has crashed and re-loading the firmware is required. |
| */ |
| static int dsps_release(struct inode *inode, struct file *file) |
| { |
| pr_debug("%s.\n", __func__); |
| |
| drv->ref_count--; |
| |
| if (drv->ref_count == 0) { |
| if (!drv->pdata->dsps_pwr_ctl_en) { |
| dsps_suspend(); |
| |
| dsps_unload(); |
| |
| dsps_power_off_handler(); |
| } |
| } |
| |
| return 0; |
| } |
| |
| const struct file_operations dsps_fops = { |
| .owner = THIS_MODULE, |
| .open = dsps_open, |
| .release = dsps_release, |
| .unlocked_ioctl = dsps_ioctl, |
| }; |
| |
| /** |
| * platform driver |
| * |
| */ |
| static int __devinit dsps_probe(struct platform_device *pdev) |
| { |
| int ret; |
| |
| pr_debug("%s.\n", __func__); |
| |
| if (pdev->dev.platform_data == NULL) { |
| pr_err("%s: platform data is NULL.\n", __func__); |
| return -ENODEV; |
| } |
| |
| drv = kzalloc(sizeof(*drv), GFP_KERNEL); |
| if (drv == NULL) { |
| pr_err("%s: kzalloc fail.\n", __func__); |
| goto alloc_err; |
| } |
| drv->pdata = pdev->dev.platform_data; |
| |
| drv->dev_class = class_create(THIS_MODULE, DRV_NAME); |
| if (drv->dev_class == NULL) { |
| pr_err("%s: class_create fail.\n", __func__); |
| goto res_err; |
| } |
| |
| ret = alloc_chrdev_region(&drv->dev_num, 0, 1, DRV_NAME); |
| if (ret) { |
| pr_err("%s: alloc_chrdev_region fail.\n", __func__); |
| goto alloc_chrdev_region_err; |
| } |
| |
| drv->dev = device_create(drv->dev_class, NULL, |
| drv->dev_num, |
| drv, DRV_NAME); |
| if (IS_ERR(drv->dev)) { |
| pr_err("%s: device_create fail.\n", __func__); |
| goto device_create_err; |
| } |
| |
| drv->cdev = cdev_alloc(); |
| if (drv->cdev == NULL) { |
| pr_err("%s: cdev_alloc fail.\n", __func__); |
| goto cdev_alloc_err; |
| } |
| cdev_init(drv->cdev, &dsps_fops); |
| drv->cdev->owner = THIS_MODULE; |
| |
| ret = cdev_add(drv->cdev, drv->dev_num, 1); |
| if (ret) { |
| pr_err("%s: cdev_add fail.\n", __func__); |
| goto cdev_add_err; |
| } |
| |
| ret = dsps_alloc_resources(pdev); |
| if (ret) { |
| pr_err("%s: failed to allocate dsps resources.\n", __func__); |
| goto cdev_add_err; |
| } |
| |
| return 0; |
| |
| cdev_add_err: |
| kfree(drv->cdev); |
| cdev_alloc_err: |
| device_destroy(drv->dev_class, drv->dev_num); |
| device_create_err: |
| unregister_chrdev_region(drv->dev_num, 1); |
| alloc_chrdev_region_err: |
| class_destroy(drv->dev_class); |
| res_err: |
| kfree(drv); |
| drv = NULL; |
| alloc_err: |
| return -ENODEV; |
| } |
| |
| static int __devexit dsps_remove(struct platform_device *pdev) |
| { |
| pr_debug("%s.\n", __func__); |
| |
| dsps_power_off_handler(); |
| dsps_free_resources(); |
| |
| cdev_del(drv->cdev); |
| kfree(drv->cdev); |
| drv->cdev = NULL; |
| device_destroy(drv->dev_class, drv->dev_num); |
| unregister_chrdev_region(drv->dev_num, 1); |
| class_destroy(drv->dev_class); |
| kfree(drv); |
| drv = NULL; |
| |
| return 0; |
| } |
| |
| static struct platform_driver dsps_driver = { |
| .probe = dsps_probe, |
| .remove = __exit_p(dsps_remove), |
| .driver = { |
| .name = "msm_dsps", |
| }, |
| }; |
| |
| /** |
| * Module Init. |
| */ |
| static int __init dsps_init(void) |
| { |
| int ret; |
| |
| pr_info("%s driver version %s.\n", DRV_NAME, DRV_VERSION); |
| |
| ret = platform_driver_register(&dsps_driver); |
| |
| if (ret) |
| pr_err("dsps_init.err=%d.\n", ret); |
| |
| return ret; |
| } |
| |
| /** |
| * Module Exit. |
| */ |
| static void __exit dsps_exit(void) |
| { |
| pr_debug("%s.\n", __func__); |
| |
| platform_driver_unregister(&dsps_driver); |
| } |
| |
| module_init(dsps_init); |
| module_exit(dsps_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("Dedicated Sensors Processor Subsystem (DSPS) driver"); |
| MODULE_AUTHOR("Amir Samuelov <amirs@codeaurora.org>"); |
| |