450 lines
10 KiB
C
450 lines
10 KiB
C
/*
|
|
* Copyright (C) 2009 Samsung Electronics
|
|
* Kyungmin Park <kyungmin.park@samsung.com>
|
|
*
|
|
* Copyright (c) 2011, The Linux Foundation. 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.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/pwm.h>
|
|
#include <linux/input.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/i2c/isa1200.h>
|
|
|
|
#define ISA1200_HCTRL0 0x30
|
|
#define HCTRL0_MODE_CTRL_BIT (3)
|
|
#define HCTRL0_OVERDRIVE_HIGH_BIT (5)
|
|
#define HCTRL0_OVERDRIVE_EN_BIT (6)
|
|
#define HCTRL0_HAP_EN (7)
|
|
#define HCTRL0_RESET 0x01
|
|
#define HCTRL1_RESET 0x4B
|
|
|
|
#define ISA1200_HCTRL1 0x31
|
|
#define HCTRL1_SMART_ENABLE_BIT (3)
|
|
#define HCTRL1_ERM_BIT (5)
|
|
#define HCTRL1_EXT_CLK_ENABLE_BIT (7)
|
|
|
|
#define ISA1200_HCTRL5 0x35
|
|
#define HCTRL5_VIB_STRT 0xD5
|
|
#define HCTRL5_VIB_STOP 0x6B
|
|
|
|
#define DIVIDER_128 (128)
|
|
#define DIVIDER_1024 (1024)
|
|
#define DIVIDE_SHIFTER_128 (7)
|
|
|
|
#define FREQ_22400 (22400)
|
|
#define FREQ_172600 (172600)
|
|
|
|
#define POR_DELAY_USEC 250
|
|
|
|
struct isa1200_chip {
|
|
const struct isa1200_platform_data *pdata;
|
|
struct i2c_client *client;
|
|
struct input_dev *input_device;
|
|
struct pwm_device *pwm;
|
|
unsigned int period_ns;
|
|
unsigned int state;
|
|
struct work_struct work;
|
|
};
|
|
|
|
static void isa1200_vib_set(struct isa1200_chip *haptic, int enable)
|
|
{
|
|
int rc;
|
|
|
|
if (enable) {
|
|
if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) {
|
|
int period_us = haptic->period_ns / NSEC_PER_USEC;
|
|
rc = pwm_config(haptic->pwm,
|
|
(period_us * haptic->pdata->duty) / 100,
|
|
period_us);
|
|
if (rc < 0)
|
|
pr_err("pwm_config fail\n");
|
|
rc = pwm_enable(haptic->pwm);
|
|
if (rc < 0)
|
|
pr_err("pwm_enable fail\n");
|
|
} else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) {
|
|
rc = i2c_smbus_write_byte_data(haptic->client,
|
|
ISA1200_HCTRL5,
|
|
HCTRL5_VIB_STRT);
|
|
if (rc < 0)
|
|
pr_err("start vibration fail\n");
|
|
}
|
|
} else {
|
|
if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE)
|
|
pwm_disable(haptic->pwm);
|
|
else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) {
|
|
rc = i2c_smbus_write_byte_data(haptic->client,
|
|
ISA1200_HCTRL5,
|
|
HCTRL5_VIB_STOP);
|
|
if (rc < 0)
|
|
pr_err("stop vibration fail\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
static int isa1200_setup(struct i2c_client *client)
|
|
{
|
|
struct isa1200_chip *haptic = i2c_get_clientdata(client);
|
|
int value, temp, rc;
|
|
|
|
gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0);
|
|
udelay(POR_DELAY_USEC);
|
|
gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 1);
|
|
|
|
value = (haptic->pdata->smart_en << HCTRL1_SMART_ENABLE_BIT) |
|
|
(haptic->pdata->is_erm << HCTRL1_ERM_BIT) |
|
|
(haptic->pdata->ext_clk_en << HCTRL1_EXT_CLK_ENABLE_BIT);
|
|
|
|
rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, value);
|
|
if (rc < 0) {
|
|
pr_err("i2c write failure\n");
|
|
return rc;
|
|
}
|
|
|
|
if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) {
|
|
temp = haptic->pdata->pwm_fd.pwm_div;
|
|
if (temp < DIVIDER_128 || temp > DIVIDER_1024 ||
|
|
temp % DIVIDER_128) {
|
|
pr_err("Invalid divider\n");
|
|
rc = -EINVAL;
|
|
goto reset_hctrl1;
|
|
}
|
|
value = ((temp >> DIVIDE_SHIFTER_128) - 1);
|
|
} else if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) {
|
|
temp = haptic->pdata->pwm_fd.pwm_freq;
|
|
if (temp < FREQ_22400 || temp > FREQ_172600 ||
|
|
temp % FREQ_22400) {
|
|
pr_err("Invalid frequency\n");
|
|
rc = -EINVAL;
|
|
goto reset_hctrl1;
|
|
}
|
|
value = ((temp / FREQ_22400) - 1);
|
|
haptic->period_ns = NSEC_PER_SEC / temp;
|
|
}
|
|
value |= (haptic->pdata->mode_ctrl << HCTRL0_MODE_CTRL_BIT) |
|
|
(haptic->pdata->overdrive_high << HCTRL0_OVERDRIVE_HIGH_BIT) |
|
|
(haptic->pdata->overdrive_en << HCTRL0_OVERDRIVE_EN_BIT) |
|
|
(haptic->pdata->chip_en << HCTRL0_HAP_EN);
|
|
|
|
rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL0, value);
|
|
if (rc < 0) {
|
|
pr_err("i2c write failure\n");
|
|
goto reset_hctrl1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
reset_hctrl1:
|
|
i2c_smbus_write_byte_data(client, ISA1200_HCTRL1,
|
|
HCTRL1_RESET);
|
|
return rc;
|
|
}
|
|
|
|
static void isa1200_worker(struct work_struct *work)
|
|
{
|
|
struct isa1200_chip *haptic;
|
|
|
|
haptic = container_of(work, struct isa1200_chip, work);
|
|
isa1200_vib_set(haptic, !!haptic->state);
|
|
}
|
|
|
|
static int isa1200_play_effect(struct input_dev *dev, void *data,
|
|
struct ff_effect *effect)
|
|
{
|
|
struct isa1200_chip *haptic = input_get_drvdata(dev);
|
|
|
|
/* support basic vibration */
|
|
haptic->state = effect->u.rumble.strong_magnitude >> 8;
|
|
if (!haptic->state)
|
|
haptic->state = effect->u.rumble.weak_magnitude >> 9;
|
|
|
|
schedule_work(&haptic->work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int isa1200_suspend(struct device *dev)
|
|
{
|
|
struct isa1200_chip *haptic = dev_get_drvdata(dev);
|
|
int rc;
|
|
|
|
cancel_work_sync(&haptic->work);
|
|
/* turn-off current vibration */
|
|
isa1200_vib_set(haptic, 0);
|
|
|
|
if (haptic->pdata->power_on) {
|
|
rc = haptic->pdata->power_on(0);
|
|
if (rc) {
|
|
pr_err("power-down failed\n");
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int isa1200_resume(struct device *dev)
|
|
{
|
|
struct isa1200_chip *haptic = dev_get_drvdata(dev);
|
|
int rc;
|
|
|
|
if (haptic->pdata->power_on) {
|
|
rc = haptic->pdata->power_on(1);
|
|
if (rc) {
|
|
pr_err("power-up failed\n");
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
isa1200_setup(haptic->client);
|
|
return 0;
|
|
}
|
|
#else
|
|
#define isa1200_suspend NULL
|
|
#define isa1200_resume NULL
|
|
#endif
|
|
|
|
static int isa1200_open(struct input_dev *dev)
|
|
{
|
|
struct isa1200_chip *haptic = input_get_drvdata(dev);
|
|
int rc;
|
|
|
|
/* device setup */
|
|
if (haptic->pdata->dev_setup) {
|
|
rc = haptic->pdata->dev_setup(true);
|
|
if (rc < 0) {
|
|
pr_err("setup failed!\n");
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* power on */
|
|
if (haptic->pdata->power_on) {
|
|
rc = haptic->pdata->power_on(true);
|
|
if (rc < 0) {
|
|
pr_err("power failed\n");
|
|
goto err_setup;
|
|
}
|
|
}
|
|
|
|
/* request gpio */
|
|
rc = gpio_is_valid(haptic->pdata->hap_en_gpio);
|
|
if (rc) {
|
|
rc = gpio_request(haptic->pdata->hap_en_gpio, "haptic_gpio");
|
|
if (rc) {
|
|
pr_err("gpio %d request failed\n",
|
|
haptic->pdata->hap_en_gpio);
|
|
goto err_power_on;
|
|
}
|
|
} else {
|
|
pr_err("Invalid gpio %d\n",
|
|
haptic->pdata->hap_en_gpio);
|
|
goto err_power_on;
|
|
}
|
|
|
|
rc = gpio_direction_output(haptic->pdata->hap_en_gpio, 0);
|
|
if (rc) {
|
|
pr_err("gpio %d set direction failed\n",
|
|
haptic->pdata->hap_en_gpio);
|
|
goto err_gpio_free;
|
|
}
|
|
|
|
/* setup registers */
|
|
rc = isa1200_setup(haptic->client);
|
|
if (rc < 0) {
|
|
pr_err("setup fail %d\n", rc);
|
|
goto err_gpio_free;
|
|
}
|
|
|
|
if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) {
|
|
haptic->pwm = pwm_request(haptic->pdata->pwm_ch_id,
|
|
haptic->client->driver->id_table->name);
|
|
if (IS_ERR(haptic->pwm)) {
|
|
pr_err("pwm request failed\n");
|
|
rc = PTR_ERR(haptic->pwm);
|
|
goto err_reset_hctrl0;
|
|
}
|
|
}
|
|
|
|
/* init workqeueue */
|
|
INIT_WORK(&haptic->work, isa1200_worker);
|
|
return 0;
|
|
|
|
err_reset_hctrl0:
|
|
i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0,
|
|
HCTRL0_RESET);
|
|
err_gpio_free:
|
|
gpio_free(haptic->pdata->hap_en_gpio);
|
|
err_power_on:
|
|
if (haptic->pdata->power_on)
|
|
haptic->pdata->power_on(0);
|
|
err_setup:
|
|
if (haptic->pdata->dev_setup)
|
|
haptic->pdata->dev_setup(false);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void isa1200_close(struct input_dev *dev)
|
|
{
|
|
struct isa1200_chip *haptic = input_get_drvdata(dev);
|
|
|
|
/* turn-off current vibration */
|
|
isa1200_vib_set(haptic, 0);
|
|
|
|
if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE)
|
|
pwm_free(haptic->pwm);
|
|
|
|
gpio_free(haptic->pdata->hap_en_gpio);
|
|
|
|
/* reset hardware registers */
|
|
i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0,
|
|
HCTRL0_RESET);
|
|
i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL1,
|
|
HCTRL1_RESET);
|
|
|
|
if (haptic->pdata->dev_setup)
|
|
haptic->pdata->dev_setup(false);
|
|
|
|
/* power-off the chip */
|
|
if (haptic->pdata->power_on)
|
|
haptic->pdata->power_on(0);
|
|
}
|
|
|
|
static int __devinit isa1200_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct isa1200_chip *haptic;
|
|
int rc;
|
|
|
|
if (!i2c_check_functionality(client->adapter,
|
|
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
|
pr_err("i2c is not supported\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (!client->dev.platform_data) {
|
|
pr_err("pdata is not avaiable\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
haptic = kzalloc(sizeof(struct isa1200_chip), GFP_KERNEL);
|
|
if (!haptic) {
|
|
pr_err("no memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
haptic->pdata = client->dev.platform_data;
|
|
haptic->client = client;
|
|
|
|
i2c_set_clientdata(client, haptic);
|
|
|
|
haptic->input_device = input_allocate_device();
|
|
if (!haptic->input_device) {
|
|
pr_err("input device alloc failed\n");
|
|
rc = -ENOMEM;
|
|
goto err_mem_alloc;
|
|
}
|
|
|
|
input_set_drvdata(haptic->input_device, haptic);
|
|
haptic->input_device->name = haptic->pdata->name ? :
|
|
"isa1200-ff-memless";
|
|
|
|
haptic->input_device->dev.parent = &client->dev;
|
|
|
|
input_set_capability(haptic->input_device, EV_FF, FF_RUMBLE);
|
|
|
|
haptic->input_device->open = isa1200_open;
|
|
haptic->input_device->close = isa1200_close;
|
|
|
|
rc = input_ff_create_memless(haptic->input_device, NULL,
|
|
isa1200_play_effect);
|
|
if (rc < 0) {
|
|
pr_err("unable to register with ff\n");
|
|
goto err_free_dev;
|
|
}
|
|
|
|
rc = input_register_device(haptic->input_device);
|
|
if (rc < 0) {
|
|
pr_err("unable to register input device\n");
|
|
goto err_ff_destroy;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_ff_destroy:
|
|
input_ff_destroy(haptic->input_device);
|
|
err_free_dev:
|
|
input_free_device(haptic->input_device);
|
|
err_mem_alloc:
|
|
kfree(haptic);
|
|
return rc;
|
|
}
|
|
|
|
static int __devexit isa1200_remove(struct i2c_client *client)
|
|
{
|
|
struct isa1200_chip *haptic = i2c_get_clientdata(client);
|
|
|
|
input_unregister_device(haptic->input_device);
|
|
kfree(haptic);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id isa1200_id_table[] = {
|
|
{"isa1200_1", 0},
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, isa1200_id_table);
|
|
|
|
static const struct dev_pm_ops isa1200_pm_ops = {
|
|
.suspend = isa1200_suspend,
|
|
.resume = isa1200_resume,
|
|
};
|
|
|
|
static struct i2c_driver isa1200_driver = {
|
|
.driver = {
|
|
.name = "isa1200-ff-memless",
|
|
.owner = THIS_MODULE,
|
|
.pm = &isa1200_pm_ops,
|
|
},
|
|
.probe = isa1200_probe,
|
|
.remove = __devexit_p(isa1200_remove),
|
|
.id_table = isa1200_id_table,
|
|
};
|
|
|
|
static int __init isa1200_init(void)
|
|
{
|
|
return i2c_add_driver(&isa1200_driver);
|
|
}
|
|
module_init(isa1200_init);
|
|
|
|
static void __exit isa1200_exit(void)
|
|
{
|
|
i2c_del_driver(&isa1200_driver);
|
|
}
|
|
module_exit(isa1200_exit);
|
|
|
|
MODULE_DESCRIPTION("isa1200 based vibrator chip driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
|