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(&gta01_vib_led);
        return 0;
}

static int gta01vib_resume(struct platform_device *dev)
{
        led_classdev_resume(&gta01_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, &gta01_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(&gta01_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(&gta01vib_driver);
}

static void __exit gta01vib_exit(void)
{
        platform_driver_unregister(&gta01vib_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