Samsung SoCs PWM and gpio connected haptic device support
It's tested on s3c64xx, s5pc1xx
Signed-off-by: Kyungmin Park <[email protected]>
---
drivers/haptic/Kconfig | 9 +
drivers/haptic/Makefile | 3 +
drivers/haptic/haptic-samsung-pwm.c | 377 +++++++++++++++++++++++++++++++++++
3 files changed, 389 insertions(+), 0 deletions(-)
create mode 100644 drivers/haptic/haptic-samsung-pwm.c
diff --git a/drivers/haptic/Kconfig b/drivers/haptic/Kconfig
index 656b022..1e9849c 100644
--- a/drivers/haptic/Kconfig
+++ b/drivers/haptic/Kconfig
@@ -11,4 +11,13 @@ config HAPTIC_CLASS
help
This option enables the haptic sysfs class in /sys/class/haptic.
+comment "Haptic drivers"
+
+config HAPTIC_SAMSUNG_PWM
+ tristate "Haptic Support for SAMSUNG PWM-controlled haptic (ISA1000)"
+ depends on HAPTIC_CLASS && (ARCH_S3C64XX || ARCH_S5PC1XX)
+ help
+ This options enables support for haptic connected to GPIO lines
+ controlled by a PWM timer on SAMSUNG CPUs.
+
endif # HAPTIC
diff --git a/drivers/haptic/Makefile b/drivers/haptic/Makefile
index d30f8cd..c691f7d 100644
--- a/drivers/haptic/Makefile
+++ b/drivers/haptic/Makefile
@@ -1,2 +1,5 @@
# Haptic Core
obj-$(CONFIG_HAPTIC_CLASS) += haptic-class.o
+
+# Drivers
+obj-$(CONFIG_HAPTIC_SAMSUNG_PWM) += haptic-samsung-pwm.o
diff --git a/drivers/haptic/haptic-samsung-pwm.c b/drivers/haptic/haptic-samsung-pwm.c
new file mode 100644
index 0000000..df6f30d
--- /dev/null
+++ b/drivers/haptic/haptic-samsung-pwm.c
@@ -0,0 +1,377 @@
+/*
+ * drivers/haptic/haptic-samsung-pwm.c
+ *
+ * Copyright (C) 2008 Samsung Electronics
+ * Kyungmin Park <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/ctype.h>
+#include <linux/haptic.h>
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/timer.h>
+
+#include "haptic.h"
+
+#define PWM_HAPTIC_PERIOD 44640
+#define PWM_HAPTIC_DEFAULT_LEVEL 2
+
+static int haptic_levels[] = { 18360, 14880, 10860, 5280, 540, };
+
+struct samsung_pwm_haptic {
+ struct haptic_classdev cdev;
+ struct work_struct work;
+ struct haptic_platform_data *pdata;
+ struct pwm_device *pwm;
+ struct timer_list timer;
+
+ int enable;
+ int powered;
+
+ int level;
+ int level_max;
+};
+
+static inline struct samsung_pwm_haptic *cdev_to_samsung_pwm_haptic(
+ struct haptic_classdev *haptic_cdev)
+{
+ return container_of(haptic_cdev, struct samsung_pwm_haptic, cdev);
+}
+
+static void samsung_pwm_haptic_power_on(struct samsung_pwm_haptic *haptic)
+{
+ if (haptic->powered)
+ return;
+ haptic->powered = 1;
+
+ if (gpio_is_valid(haptic->pdata->gpio))
+ gpio_set_value(haptic->pdata->gpio, 1);
+
+ pwm_enable(haptic->pwm);
+}
+
+static void samsung_pwm_haptic_power_off(struct samsung_pwm_haptic *haptic)
+{
+ if (!haptic->powered)
+ return;
+ haptic->powered = 0;
+
+ if (gpio_is_valid(haptic->pdata->gpio))
+ gpio_set_value(haptic->pdata->gpio, 0);
+
+ pwm_disable(haptic->pwm);
+}
+
+static int samsung_pwm_haptic_set_pwm_cycle(struct samsung_pwm_haptic *haptic)
+{
+ int duty = haptic_levels[haptic->level];
+ return pwm_config(haptic->pwm, duty, PWM_HAPTIC_PERIOD);
+}
+
+static void samsung_pwm_haptic_work(struct work_struct *work)
+{
+ struct samsung_pwm_haptic *haptic;
+ int r;
+
+ haptic = container_of(work, struct samsung_pwm_haptic, work);
+
+ if (haptic->enable) {
+ r = samsung_pwm_haptic_set_pwm_cycle(haptic);
+ if (r) {
+ dev_dbg(haptic->cdev.dev, "set_pwm_cycle failed\n");
+ return;
+ }
+ samsung_pwm_haptic_power_on(haptic);
+ } else {
+ samsung_pwm_haptic_power_off(haptic);
+ }
+}
+
+static void samsung_pwm_haptic_timer(unsigned long data)
+{
+ struct samsung_pwm_haptic *haptic = (struct samsung_pwm_haptic *)data;
+
+ haptic->enable = 0;
+ samsung_pwm_haptic_power_off(haptic);
+}
+
+static void samsung_pwm_haptic_set(struct haptic_classdev *haptic_cdev,
+ enum haptic_value value)
+{
+ struct samsung_pwm_haptic *haptic =
+ cdev_to_samsung_pwm_haptic(haptic_cdev);
+
+ switch (value) {
+ case HAPTIC_OFF:
+ haptic->enable = 0;
+ break;
+ case HAPTIC_HALF:
+ case HAPTIC_FULL:
+ default:
+ haptic->enable = 1;
+ break;
+ }
+
+ schedule_work(&haptic->work);
+}
+
+static enum haptic_value samsung_pwm_haptic_get(
+ struct haptic_classdev *haptic_cdev)
+{
+ struct samsung_pwm_haptic *haptic =
+ cdev_to_samsung_pwm_haptic(haptic_cdev);
+
+ if (haptic->enable)
+ return HAPTIC_FULL;
+
+ return HAPTIC_OFF;
+}
+
+#define ATTR_DEF_SHOW(name) \
+static ssize_t samsung_pwm_haptic_show_##name(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev); \
+ struct samsung_pwm_haptic *haptic =\
+ cdev_to_samsung_pwm_haptic(haptic_cdev); \
+ \
+ return sprintf(buf, "%u\n", haptic->name) + 1; \
+}
+
+#define ATTR_DEF_STORE(name) \
+static ssize_t samsung_pwm_haptic_store_##name(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t size) \
+{ \
+ struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev); \
+ struct samsung_pwm_haptic *haptic =\
+ cdev_to_samsung_pwm_haptic(haptic_cdev); \
+ ssize_t ret = -EINVAL; \
+ unsigned long val; \
+ \
+ ret = strict_strtoul(buf, 10, &val); \
+ if (ret == 0) { \
+ ret = size; \
+ haptic->name = val; \
+ schedule_work(&haptic->work); \
+ } \
+ \
+ return ret; \
+}
+
+ATTR_DEF_SHOW(enable);
+ATTR_DEF_STORE(enable);
+static DEVICE_ATTR(enable, 0644, samsung_pwm_haptic_show_enable,
+ samsung_pwm_haptic_store_enable);
+
+static ssize_t samsung_pwm_haptic_store_level(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev);
+ struct samsung_pwm_haptic *haptic =
+ cdev_to_samsung_pwm_haptic(haptic_cdev);
+ ssize_t ret = -EINVAL;
+ unsigned long val;
+
+ ret = strict_strtoul(buf, 10, &val);
+ if (ret == 0) {
+ ret = size;
+ if (haptic->level_max < val)
+ val = haptic->level_max;
+ haptic->level = val;
+ schedule_work(&haptic->work);
+ }
+
+ return ret;
+}
+ATTR_DEF_SHOW(level);
+static DEVICE_ATTR(level, 0644, samsung_pwm_haptic_show_level,
+ samsung_pwm_haptic_store_level);
+
+ATTR_DEF_SHOW(level_max);
+static DEVICE_ATTR(level_max, 0444, samsung_pwm_haptic_show_level_max, NULL);
+
+static ssize_t samsung_pwm_haptic_store_oneshot(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev);
+ struct samsung_pwm_haptic *haptic =
+ cdev_to_samsung_pwm_haptic(haptic_cdev);
+ ssize_t ret = -EINVAL;
+ unsigned long val;
+
+ ret = strict_strtoul(buf, 10, &val);
+ if (ret == 0) {
+ ret = size;
+ haptic->enable = 1;
+ mod_timer(&haptic->timer, jiffies + val * HZ / 1000);
+ schedule_work(&haptic->work);
+ }
+
+ return ret;
+}
+static DEVICE_ATTR(oneshot, 0200, NULL, samsung_pwm_haptic_store_oneshot);
+
+static struct attribute *haptic_attributes[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_level.attr,
+ &dev_attr_level_max.attr,
+ &dev_attr_oneshot.attr,
+ NULL,
+};
+
+static const struct attribute_group haptic_group = {
+ .attrs = haptic_attributes,
+};
+
+static int __devinit samsung_pwm_haptic_probe(struct platform_device *pdev)
+{
+ struct haptic_platform_data *pdata = pdev->dev.platform_data;
+ struct samsung_pwm_haptic *haptic;
+ int ret;
+
+ haptic = kzalloc(sizeof(struct samsung_pwm_haptic), GFP_KERNEL);
+ if (!haptic) {
+ dev_err(&pdev->dev, "No memory for device\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, haptic);
+ haptic->cdev.set = samsung_pwm_haptic_set;
+ haptic->cdev.get = samsung_pwm_haptic_get;
+ haptic->cdev.show_enable = samsung_pwm_haptic_show_enable;
+ haptic->cdev.store_enable = samsung_pwm_haptic_store_enable;
+ haptic->cdev.store_oneshot = samsung_pwm_haptic_store_oneshot;
+ haptic->cdev.show_level = samsung_pwm_haptic_show_level;
+ haptic->cdev.store_level = samsung_pwm_haptic_store_level;
+ haptic->cdev.show_level_max = samsung_pwm_haptic_show_level_max;
+ haptic->cdev.name = pdata->name;
+ haptic->pdata = pdata;
+ haptic->enable = 0;
+ haptic->level = PWM_HAPTIC_DEFAULT_LEVEL;
+ haptic->level_max = ARRAY_SIZE(haptic_levels);
+
+ if (pdata->setup_pin)
+ pdata->setup_pin();
+
+ INIT_WORK(&haptic->work, samsung_pwm_haptic_work);
+
+ /* register our new haptic device */
+ ret = haptic_classdev_register(&pdev->dev, &haptic->cdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "haptic_classdev_register failed\n");
+ goto error_classdev;
+ }
+
+ haptic->pwm = pwm_request(pdata->pwm_timer, "haptic");
+ if (IS_ERR(haptic->pwm)) {
+ dev_err(&pdev->dev, "unable to request PWM for haptic\n");
+ ret = PTR_ERR(haptic->pwm);
+ goto err_pwm;
+ } else
+ dev_dbg(&pdev->dev, "got pwm for haptic\n");
+
+ ret = sysfs_create_group(&haptic->cdev.dev->kobj, &haptic_group);
+ if (ret)
+ goto error_enable;
+
+ if (gpio_is_valid(pdata->gpio)) {
+ printk(KERN_INFO "Motor enable gpio %d\n", pdata->gpio);
+ ret = gpio_request(pdata->gpio, "haptic enable");
+ if (ret)
+ goto error_gpio;
+ gpio_direction_output(pdata->gpio, 0);
+ }
+
+ init_timer(&haptic->timer);
+ haptic->timer.data = (unsigned long)haptic;
+ haptic->timer.function = &samsung_pwm_haptic_timer;
+
+ printk(KERN_INFO "samsung %s registed\n", pdata->name);
+ return 0;
+
+error_gpio:
+ gpio_free(pdata->gpio);
+error_enable:
+ sysfs_remove_group(&haptic->cdev.dev->kobj, &haptic_group);
+err_pwm:
+ pwm_free(haptic->pwm);
+error_classdev:
+ haptic_classdev_unregister(&haptic->cdev);
+ kfree(haptic);
+ return ret;
+}
+
+static int __devexit samsung_pwm_haptic_remove(struct platform_device *pdev)
+{
+ struct samsung_pwm_haptic *haptic = platform_get_drvdata(pdev);
+
+ samsung_pwm_haptic_set(&haptic->cdev, HAPTIC_OFF);
+ del_timer_sync(&haptic->timer);
+
+ if (haptic->pdata->gpio)
+ gpio_free(haptic->pdata->gpio);
+ device_remove_file(haptic->cdev.dev, &dev_attr_enable);
+ haptic_classdev_unregister(&haptic->cdev);
+ kfree(haptic);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int samsung_pwm_haptic_suspend(
+ struct platform_device *pdev, pm_message_t state)
+{
+ struct samsung_pwm_haptic *haptic = platform_get_drvdata(pdev);
+
+ haptic_classdev_suspend(&haptic->cdev);
+ return 0;
+}
+
+static int samsung_pwm_haptic_resume(struct platform_device *pdev)
+{
+ struct samsung_pwm_haptic *haptic = platform_get_drvdata(pdev);
+
+ haptic_classdev_resume(&haptic->cdev);
+ return 0;
+}
+#else
+#define samsung_pwm_haptic_suspend NULL
+#define samsung_pwm_haptic_resume NULL
+#endif
+
+static struct platform_driver samsung_pwm_haptic_driver = {
+ .probe = samsung_pwm_haptic_probe,
+ .remove = samsung_pwm_haptic_remove,
+ .suspend = samsung_pwm_haptic_suspend,
+ .resume = samsung_pwm_haptic_resume,
+ .driver = {
+ .name = "samsung_pwm_haptic",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init samsung_pwm_haptic_init(void)
+{
+ return platform_driver_register(&samsung_pwm_haptic_driver);
+}
+module_init(samsung_pwm_haptic_init);
+
+static void __exit samsung_pwm_haptic_exit(void)
+{
+ platform_driver_unregister(&samsung_pwm_haptic_driver);
+}
+module_exit(samsung_pwm_haptic_exit);
+
+MODULE_AUTHOR("Kyungmin Park <[email protected]>");
+MODULE_DESCRIPTION("samsung PWM haptic driver");
+MODULE_LICENSE("GPL");
--
1.5.3.3
Adding Bill Gatliff.
Hi Kyungmin,
Most of the driver looks fine. I have only one generic comment, but
not for this driver:
- Someone should really work on getting PWM framework (may be the one
written by Bill Gatliff) into the kernel, because
if we don't do this and if new PWM controller provides it's own APIs
then we can't leverage the chips using them, like the
case here for ISA1000 and ISA1200. These chips are using the pwm
APIs exported by Samsung PWM controller, but it would
be painful to integrate other PWM controller if that has slightly
little different API due to its nature.
So, it would be better to get one generic PWM framework mainline in
the kernel.
> +static int __devexit samsung_pwm_haptic_remove(struct platform_device *pdev)
> +{
> + ? ? ? struct samsung_pwm_haptic *haptic = platform_get_drvdata(pdev);
> +
> + ? ? ? samsung_pwm_haptic_set(&haptic->cdev, HAPTIC_OFF);
> + ? ? ? del_timer_sync(&haptic->timer);
cancel_work_sync ?
--
---Trilok Soni
http://triloksoni.wordpress.com
http://www.linkedin.com/in/triloksoni
Trilok Soni wrote:
> Adding Bill Gatliff.
>
> Hi Kyungmin,
>
> Most of the driver looks fine. I have only one generic comment, but
> not for this driver:
>
> - Someone should really work on getting PWM framework (may be the one
> written by Bill Gatliff) into the kernel, because
> if we don't do this and if new PWM controller provides it's own APIs
> then we can't leverage the chips using them, like the
> case here for ISA1000 and ISA1200. These chips are using the pwm
> APIs exported by Samsung PWM controller, but it would
> be painful to integrate other PWM controller if that has slightly
> little different API due to its nature.
>
> So, it would be better to get one generic PWM framework mainline in
> the kernel.
>
I can repost for review, if you like.
The only objection I received last time, IIRC, was that I proffered it
as a "generic" API but only provided support for a couple of AT91
parts. I haven't had much time to add other support, so I haven't
pushed for inclusion.
I would be happy to coordinate the development/migration of support to
the API. I can still do the AT91 parts and probably PXA, maybe
OMAP3430/Beagleboard. But I would need others to help me with the rest.
b.g.
--
Bill Gatliff
[email protected]
Hi,
I agree use the generic PWM framework. But it's not support on Samsung SoCs yet.
I also wrote the OMAP driver. Of course it uses the OMAP specific PWM drivers.
I think after full integration of PWM frameworks as GPIO does, It will
be replace with new PWM frameworks.
Thank you,
Kyungmin Park
On Fri, Oct 9, 2009 at 3:36 AM, Bill Gatliff <[email protected]> wrote:
> Trilok Soni wrote:
>>
>> Adding Bill Gatliff.
>>
>> Hi Kyungmin,
>>
>> Most of the driver looks fine. I have only one generic comment, but
>> not for this driver:
>>
>> - Someone should really work on getting PWM framework (may be the one
>> written by Bill Gatliff) into the kernel, because
>> ?if we don't do this and if new PWM controller provides it's own APIs
>> then we can't leverage the chips using them, like the
>> ?case here for ISA1000 and ISA1200. These chips are using the pwm
>> APIs exported by Samsung PWM controller, but it would
>> ?be painful to integrate other PWM controller if that has slightly
>> little different API due to its nature.
>>
>> ?So, it would be better to get one generic PWM framework mainline in
>> the kernel.
>>
>
> I can repost for review, if you like.
>
> The only objection I received last time, IIRC, was that I proffered it as a
> "generic" API but only provided support for a couple of AT91 parts. ?I
> haven't had much time to add other support, so I haven't pushed for
> inclusion.
>
> I would be happy to coordinate the development/migration of support to the
> API. ?I can still do the AT91 parts and probably PXA, maybe
> OMAP3430/Beagleboard. ?But I would need others to help me with the rest.
>
> b.g.
>
> --
> Bill Gatliff
> [email protected]
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to [email protected]
> More majordomo info at ?http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at ?http://www.tux.org/lkml/
>
Hi Kyungmin,
On Fri, Oct 9, 2009 at 5:21 AM, Kyungmin Park <[email protected]> wrote:
> Hi,
>
> I agree use the generic PWM framework. But it's not support on Samsung SoCs yet.
>
> I also wrote the OMAP driver. Of course it uses the OMAP specific PWM drivers.
>
> I think after full integration of PWM frameworks as GPIO does, It will
> be replace with new PWM frameworks.
Yes, I don't want to stop Haptics class and drivers because of no.
generic PWM framework yet in mainline. Someone could write down OMAP
and Samsung PWM controller drivers once Bill refreshes his patchesets
with latest kernel version.
--
---Trilok Soni
http://triloksoni.wordpress.com
http://www.linkedin.com/in/triloksoni
Hi Bill,
On Fri, Oct 9, 2009 at 12:06 AM, Bill Gatliff <[email protected]> wrote:
> Trilok Soni wrote:
>>
>> Adding Bill Gatliff.
>>
>> Hi Kyungmin,
>>
>> Most of the driver looks fine. I have only one generic comment, but
>> not for this driver:
>>
>> - Someone should really work on getting PWM framework (may be the one
>> written by Bill Gatliff) into the kernel, because
>> ?if we don't do this and if new PWM controller provides it's own APIs
>> then we can't leverage the chips using them, like the
>> ?case here for ISA1000 and ISA1200. These chips are using the pwm
>> APIs exported by Samsung PWM controller, but it would
>> ?be painful to integrate other PWM controller if that has slightly
>> little different API due to its nature.
>>
>> ?So, it would be better to get one generic PWM framework mainline in
>> the kernel.
>>
>
> I can repost for review, if you like.
>
> The only objection I received last time, IIRC, was that I proffered it as a
> "generic" API but only provided support for a couple of AT91 parts. ?I
> haven't had much time to add other support, so I haven't pushed for
> inclusion.
>
> I would be happy to coordinate the development/migration of support to the
> API. ?I can still do the AT91 parts and probably PXA, maybe
> OMAP3430/Beagleboard. ?But I would need others to help me with the rest.
Two drivers are OK to begin with and for mainline. It is expected that
framework author may not have all parts with him to demonstrate a
thumbrule of atleast three driver per framework in-order to make it
mainline to test most the framework logic.
If mainline authors doesn't agree still, we can still target this
through drivers\stagic area through Greg KH. Please post your
refreshed version.
--
---Trilok Soni
http://triloksoni.wordpress.com
http://www.linkedin.com/in/triloksoni
Trilok Soni wrote:
> Please post your refreshed version.
>
Ok. I'll try to rebase it to the latest over the weekend.
b.g.
--
Bill Gatliff
[email protected]
Guys:
git://git.billgatliff.com/pwm.git
See Documentation/pwm.txt
b.g.
--
Bill Gatliff
[email protected]