| /* drivers/motor/dcmotor.c |
| |
| * Copyright (C) 2015 Samsung Electronics Co. Ltd. All Rights Reserved. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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. |
| * |
| */ |
| |
| |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/hrtimer.h> |
| #include <linux/err.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/of_gpio.h> |
| #include <linux/wakelock.h> |
| #include <linux/workqueue.h> |
| |
| #include "../staging/android/timed_output.h" |
| |
| #define SEC_VIB_NAME "sec_vib" |
| #define INTERNAL_LDO 0 |
| #define EXTERNAL_LDO 1 |
| |
| #define LDO_EN 1 |
| #define LDO_DIS 0 |
| struct sec_vib_pdata { |
| const char *regulator; |
| int max_timeout; |
| |
| int ldo_type; |
| int gpio_ldo_en; |
| }; |
| |
| struct sec_vib_drvdata { |
| struct regulator *regulator; |
| struct timed_output_dev dev; |
| struct hrtimer timer; |
| struct workqueue_struct *workqueue; |
| struct work_struct work; |
| spinlock_t lock; |
| bool running; |
| int max_timeout; |
| int timeout; |
| int ldo_type; |
| int gpio_ldo_en; |
| }; |
| |
| int sec_vib_vdd_en(struct timed_output_dev *dev, bool en) |
| { |
| struct sec_vib_drvdata *ddata = |
| container_of(dev, struct sec_vib_drvdata, dev); |
| int ret = 0; |
| if (en) { |
| if (ddata->ldo_type == INTERNAL_LDO) { /* pmic ldo */ |
| ret = regulator_enable(ddata->regulator); |
| } |
| else if (ddata->ldo_type == EXTERNAL_LDO) { /* external ldo */ |
| ret = gpio_direction_output(ddata->gpio_ldo_en, 1); |
| } |
| } |
| else { |
| if (ddata->ldo_type == INTERNAL_LDO) { /* pmic ldo */ |
| ret = regulator_disable(ddata->regulator); |
| } |
| else if (ddata->ldo_type == EXTERNAL_LDO) { /* external ldo */ |
| ret = gpio_direction_output(ddata->gpio_ldo_en, 0); |
| } |
| } |
| return 0; |
| } |
| static enum hrtimer_restart sec_vib_timer_func(struct hrtimer *timer) |
| { |
| struct sec_vib_drvdata *ddata = |
| container_of(timer, struct sec_vib_drvdata, timer); |
| |
| ddata->timeout = 0; |
| queue_work(ddata->workqueue, &ddata->work); |
| return HRTIMER_NORESTART; |
| } |
| |
| static int sec_vib_get_time(struct timed_output_dev *dev) |
| { |
| struct sec_vib_drvdata *ddata = |
| container_of(dev, struct sec_vib_drvdata, dev); |
| |
| if (hrtimer_active(&ddata->timer)) { |
| ktime_t r = hrtimer_get_remaining(&ddata->timer); |
| struct timeval t = ktime_to_timeval(r); |
| return t.tv_sec * 1000 + t.tv_usec / 1000; |
| } else |
| return 0; |
| } |
| |
| static void sec_vib_enable(struct timed_output_dev *dev, int value) |
| { |
| struct sec_vib_drvdata *ddata = |
| container_of(dev, struct sec_vib_drvdata, dev); |
| unsigned long flags; |
| |
| printk("[VIB] %s, timedout = %d \n", __func__, value); |
| hrtimer_cancel(&ddata->timer); |
| |
| if (value > 0) { |
| |
| if (value > ddata->max_timeout) |
| value = ddata->max_timeout; |
| |
| ddata->timeout = value; |
| queue_work(ddata->workqueue, &ddata->work); |
| |
| spin_lock_irqsave(&ddata->lock, flags); |
| hrtimer_start(&ddata->timer, |
| ktime_set(value / 1000, (value % 1000) * 1000000), |
| HRTIMER_MODE_REL); |
| |
| spin_unlock_irqrestore(&ddata->lock, flags); |
| } else { |
| ddata->timeout = 0; |
| queue_work(ddata->workqueue, &ddata->work); |
| } |
| } |
| |
| static void sec_vib_work(struct work_struct *work) |
| { |
| struct sec_vib_drvdata *ddata = |
| container_of(work, struct sec_vib_drvdata, work); |
| |
| printk("[VIB] %s, timedout = %d , running = %d \n", __func__, ddata->timeout, ddata->running ); |
| |
| if (ddata->timeout > 0) { |
| if (ddata->running) |
| return; |
| sec_vib_vdd_en(&ddata->dev, LDO_EN); |
| ddata->running = true; |
| } else { |
| if (!ddata->running) |
| return; |
| sec_vib_vdd_en(&ddata->dev, LDO_DIS); |
| ddata->running = false; |
| } |
| |
| return; |
| } |
| |
| #if defined(CONFIG_OF) |
| static struct sec_vib_pdata *sec_vib_get_dt(struct device *dev) |
| { |
| struct device_node *node, *child_node; |
| struct sec_vib_pdata *pdata; |
| int ret = 0; |
| |
| node = dev->of_node; |
| if (!node) { |
| ret = -ENODEV; |
| goto err_out; |
| } |
| |
| child_node = of_get_next_child(node, NULL); |
| if (!child_node) { |
| printk("[VIB] failed to get dt node\n"); |
| ret = -EINVAL; |
| goto err_out; |
| } |
| |
| pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) { |
| printk("[VIB] failed to alloc\n"); |
| ret = -ENOMEM; |
| goto err_out; |
| } |
| |
| of_property_read_u32(child_node, "sec_vib,max_timeout", &pdata->max_timeout); |
| of_property_read_u32(child_node, "sec_vib,ldo_type", &pdata->ldo_type); |
| if (pdata->ldo_type == INTERNAL_LDO) { |
| of_property_read_string(child_node, "sec_vib,regulator", &pdata->regulator); |
| } |
| else if (pdata->ldo_type == EXTERNAL_LDO) { |
| pdata->gpio_ldo_en = of_get_named_gpio(child_node, "sec_vib,ldo_en", 0); |
| if (!gpio_is_valid(pdata->gpio_ldo_en)) { |
| printk("[VIB]failed to get ldo_en gpio\n"); |
| } else { |
| ret = gpio_request(pdata->gpio_ldo_en, "mot_ldo_en"); |
| if (ret) { |
| printk("[VIB]: %s : motor gpio request fail = %d \n",__func__, ret); |
| goto err_gpio_req; |
| } |
| ret = gpio_direction_output(pdata->gpio_ldo_en, 0); |
| } |
| } |
| |
| return pdata; |
| |
| err_gpio_req: |
| kfree(pdata); |
| err_out: |
| return ERR_PTR(ret); |
| } |
| #endif |
| |
| |
| |
| |
| static int sec_vib_probe(struct platform_device *pdev) |
| { |
| struct sec_vib_pdata *pdata = pdev->dev.platform_data; |
| struct sec_vib_drvdata *ddata; |
| int ret = 0; |
| |
| pr_info("[VIB] %s \n", __func__); |
| if (!pdata) { |
| #if defined(CONFIG_OF) |
| pdata = sec_vib_get_dt(&pdev->dev); |
| if (IS_ERR(pdata)) { |
| printk(KERN_ERR "[VIB] there is no device tree!\n"); |
| ret = -ENODEV; |
| goto err_pdata; |
| } |
| #else |
| ret = -ENODEV; |
| printk(KERN_ERR "[VIB] there is no platform data!\n"); |
| goto err_pdata; |
| #endif |
| } |
| |
| ddata = kzalloc(sizeof(struct sec_vib_drvdata), GFP_KERNEL); |
| if (!ddata) { |
| ret = -ENOMEM; |
| printk(KERN_ERR "[VIB] Failed to memory alloc \n"); |
| goto err_ddata; |
| } |
| |
| ddata->ldo_type = pdata->ldo_type; |
| if (ddata->ldo_type == INTERNAL_LDO) { |
| ddata->regulator = regulator_get(NULL, pdata->regulator); |
| if (IS_ERR(ddata->regulator)) { |
| printk(KERN_ERR "[VIB] failed get %s\n", pdata->regulator); |
| ret = PTR_ERR(ddata->regulator); |
| goto err_regulator_get; |
| } |
| } |
| else if (ddata->ldo_type == EXTERNAL_LDO) { |
| ddata->gpio_ldo_en = pdata->gpio_ldo_en; |
| } |
| |
| hrtimer_init(&ddata->timer, CLOCK_MONOTONIC, |
| HRTIMER_MODE_REL); |
| |
| ddata->timer.function = sec_vib_timer_func; |
| spin_lock_init(&ddata->lock); |
| |
| ddata->max_timeout = pdata->max_timeout; |
| |
| ddata->workqueue = create_singlethread_workqueue("sec_vib_work"); |
| if (!ddata->workqueue) { |
| ret = -EFAULT; |
| printk(KERN_ERR "[VIB] Failed to create workqueue \n"); |
| goto err_work_queue; |
| } |
| INIT_WORK(&(ddata->work), sec_vib_work); |
| |
| ddata->dev.name = "vibrator"; |
| ddata->dev.get_time = sec_vib_get_time; |
| ddata->dev.enable = sec_vib_enable; |
| |
| ret = timed_output_dev_register(&ddata->dev); |
| if (ret < 0) |
| goto err_timed_output_register; |
| |
| platform_set_drvdata(pdev, ddata); |
| |
| return 0; |
| |
| err_timed_output_register: |
| destroy_workqueue(ddata->workqueue); |
| err_work_queue: |
| if (ddata->ldo_type == INTERNAL_LDO) { /* pmic ldo */ |
| regulator_put(ddata->regulator); |
| } |
| err_regulator_get: |
| kfree(ddata); |
| err_ddata: |
| #if defined(CONFIG_OF) |
| kfree(pdata); |
| #endif |
| err_pdata: |
| return ret; |
| } |
| |
| static int sec_vib_remove(struct platform_device *pdev) |
| { |
| struct sec_vib_drvdata *ddata = platform_get_drvdata(pdev); |
| |
| destroy_workqueue(ddata->workqueue); |
| if (ddata->ldo_type == INTERNAL_LDO) { /* pmic ldo */ |
| regulator_put(ddata->regulator); |
| } |
| timed_output_dev_unregister(&ddata->dev); |
| kfree(ddata); |
| return 0; |
| } |
| |
| #if defined(CONFIG_OF) |
| static struct of_device_id sec_vib_dt_ids[] = { |
| { .compatible = "sec_vib" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, sec_vib_dt_ids); |
| #endif /* CONFIG_OF */ |
| |
| static struct platform_driver sec_vib_driver = { |
| .probe = sec_vib_probe, |
| .remove = sec_vib_remove, |
| .driver = { |
| .name = SEC_VIB_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(sec_vib_dt_ids), |
| }, |
| }; |
| |
| static int __init sec_vib_init(void) |
| { |
| return platform_driver_register(&sec_vib_driver); |
| } |
| module_init(sec_vib_init); |
| |
| static void __exit sec_vib_exit(void) |
| { |
| platform_driver_unregister(&sec_vib_driver); |
| } |
| module_exit(sec_vib_exit); |
| |
| MODULE_AUTHOR("Samsung Electronics"); |
| MODULE_DESCRIPTION("dc motor driver"); |
| MODULE_LICENSE("GPL"); |
| |
| |