vibrator PWM support
Javi Roman
javiroman at kernel-labs.org
Sun Sep 9 12:09:05 CEST 2007
Hello,
I've added PWM support for Neo1973 vibrator driver
(http://bugzilla.openmoko.org/cgi-bin/bugzilla/show_bug.cgi?id=76).
Nevertheless this code is not a very good solution, because it's a
derivated code of gta01_bl.c and the final target is an abstraction of
PWM timer code (i'm woking on that). Also the suspend code is not ended.
On the other hand I request feedback because actually I don't know the
good combination of prescaler+divider+counter value. The code shows
empirical values, the vibrator works with:
echo 255 > /sys/class/leds/gta01\:vibrator/brightness (99% duty cycle,
full power)
echo 128 > /sys/class/leds/gta01\:vibrator/brightness (50% duty cycle,
medium power)
echo 0 > /sys/class/leds/gta01\:vibrator/brightness (0% duty cycle, off power)
Please let me know what's wrong, or some other kind of feedback.
BTW, I wonder if it's better to create a new class driver for vibrator
rather than led class.
Regards,
-Javi
[code]
/*
* LED driver for the FIC GTA01 (Neo1973) GSM Phone Vibrator
*
* (C) 2006-2007 by OpenMoko, Inc.
* Author: Harald Welte <laforge at openmoko.org>
* 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 as
* published by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/clk.h>
#include <asm-arm/io.h>
#include <asm/arch/hardware.h>
#include <asm/mach-types.h>
#include <asm/arch/regs-timer.h>
#include <asm/arch/gta01.h>
#define COUNTER 256
struct gta01_vib_priv
{
struct led_classdev cdev;
unsigned int gpio;
struct mutex mutex;
unsigned int has_pwm;
struct clk *clk;
};
static inline struct gta01_vib_priv *pdev_to_vpriv(struct platform_device *dev)
{
return platform_get_drvdata(dev);
}
static inline struct gta01_vib_priv *to_vpriv(struct led_classdev *led_cdev)
{
return dev_get_drvdata(led_cdev->class_dev->dev);
}
static void gta01vib_vib_set(struct led_classdev *led_cdev, enum
led_brightness value)
{
struct gta01_vib_priv *vp = to_vpriv(led_cdev);
/*
* value == 255 -> 99% duty cycle (full voltage)
* value == 128 -> 50% duty cycle (middle voltage)
* value == 0 -> 0% duty cycle (zero voltage)
*/
mutex_lock(&vp->mutex);
if (vp->has_pwm) {
__raw_writel(value, S3C2410_TCMPB(3));
s3c2410_gpio_cfgpin(vp->gpio, S3C2410_GPB3_TOUT3);
} else {
if (value)
s3c2410_gpio_setpin(vp->gpio, 1);
else
s3c2410_gpio_setpin(vp->gpio, 0);
}
mutex_unlock(&vp->mutex);
}
static struct led_classdev gta01_vib_led = {
.name = "gta01:vibrator",
.brightness_set = gta01vib_vib_set,
};
#ifdef CONFIG_PM
static int gta01vib_suspend(struct platform_device *dev, pm_message_t state)
{
led_classdev_suspend(>a01_vib_led);
return 0;
}
static int gta01vib_resume(struct platform_device *dev)
{
led_classdev_resume(>a01_vib_led);
return 0;
}
#endif
static void gta01vib_init_hw(struct platform_device *pdev)
{
unsigned long pclk, tcon, tcfg0, tcfg1, tcnt;
struct gta01_vib_priv *vp = pdev_to_vpriv(pdev);
pclk = clk_get_rate(vp->clk);
printk(KERN_INFO "PCLK rate %luHz\n", pclk);
/* read the current timer control registers bits */
tcon = __raw_readl(S3C2410_TCON);
tcfg1 = __raw_readl(S3C2410_TCFG1);
tcfg0 = __raw_readl(S3C2410_TCFG0);
/* divider value */
tcfg1 &= ~S3C2410_TCFG1_MUX3_MASK;
tcfg1 |= S3C2410_TCFG1_MUX3_DIV8;
/* prescaler value */
tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK;
tcfg0 |= 0xff;
__raw_writel(tcfg0, S3C2410_TCFG0);
__raw_writel(tcfg1, S3C2410_TCFG1);
/* timer count and compare buffer initial values */
tcnt = COUNTER;
/* ensure timer is stopped */
tcon &= 0xffffff00;
tcon |= S3C2410_TCON_T3RELOAD;
tcon |= S3C2410_TCON_T3MANUALUPD;
__raw_writel(tcnt, S3C2410_TCNTB(3));
__raw_writel(tcnt, S3C2410_TCMPB(3));
__raw_writel(tcon, S3C2410_TCON);
/* start the timer */
tcon |= S3C2410_TCON_T3START;
tcon &= ~S3C2410_TCON_T3MANUALUPD;
__raw_writel(tcon, S3C2410_TCON);
return;
}
static int __init gta01vib_probe(struct platform_device *pdev)
{
struct gta01_vib_priv *vp;
struct resource *r;
if (!machine_is_neo1973_gta01())
return -EIO;
r = platform_get_resource(pdev, 0, 0);
if (!r || !r->start)
return -EIO;
vp = kzalloc(sizeof(struct gta01_vib_priv), GFP_KERNEL);
if (!vp)
return -ENOMEM;
platform_set_drvdata(pdev, vp);
vp->gpio = r->start;
/* TOUT3 */
if (vp->gpio == S3C2410_GPB3) {
vp->has_pwm = 1;
/* use s3c_device_timer3 for PWM */
vp->clk = clk_get(NULL, "timers");
if (IS_ERR(vp->clk))
return PTR_ERR(vp->clk);
clk_enable(vp->clk);
gta01vib_init_hw(pdev);
}
mutex_init(&vp->mutex);
printk(KERN_INFO "GTA01 Vibrator Driver Initialized.\n");
return led_classdev_register(&pdev->dev, >a01_vib_led);
}
static int gta01vib_remove(struct platform_device *pdev)
{
struct gta01_vib_priv *vp = pdev_to_vpriv(pdev);
unsigned long tcon;
if (vp->has_pwm) {
/* stop this timer */
tcon = __raw_readl(S3C2410_TCON);
tcon &= 0xffffff00;
__raw_writel(tcon, S3C2410_TCON);
clk_disable(vp->clk);
clk_put(vp->clk);
}
led_classdev_unregister(>a01_vib_led);
platform_set_drvdata(pdev, NULL);
kfree(vp);
mutex_destroy(&vp->mutex);
printk(KERN_INFO "GTA01 Vibrator Driver Unloaded\n");
return 0;
}
static struct platform_driver gta01vib_driver = {
.probe = gta01vib_probe,
.remove = gta01vib_remove,
#ifdef CONFIG_PM
.suspend = gta01vib_suspend,
.resume = gta01vib_resume,
#endif
.driver = {
.name = "gta01-led",
},
};
static int __init gta01vib_init(void)
{
return platform_driver_register(>a01vib_driver);
}
static void __exit gta01vib_exit(void)
{
platform_driver_unregister(>a01vib_driver);
}
module_init(gta01vib_init);
module_exit(gta01vib_exit);
MODULE_AUTHOR("Harald Welte <laforge at openmoko.org>");
MODULE_DESCRIPTION("FIC GTA01 Vibrator driver");
MODULE_LICENSE("GPL");
[code]
More information about the openmoko-kernel
mailing list