blob: 4c13285e26af315be82a77f4918f274c888fb2d7 [file] [log] [blame]
/* Copyright (c) 2016, 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.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/platform_data/drv8830.h>
#include <linux/jiffies.h>
#define PULSE_DURATION_MS 100
#define DEFAULT_RTOS_POLL_INTERVAL_MS 10
#define DEFAULT_RTOS_POLL_TRY_NUMBER 100
#ifdef CONFIG_TI_DRV8830_QUERY_RTOS
// This function is not exported by freertos header.
extern bool freertos_cpu1_exited(void);
#endif
struct drv8830_data {
struct i2c_client *client;
struct work_struct pulse_work;
struct delayed_work poll_rtos_work;
s32 fault_gpio;
u8 vset;
u8 polarity;
u8 open_target;
u8 open_state;
u8 initialized;
u8 skip_init;
u8 default_state;
u8 probed;
unsigned long rtos_poll_interval_jiffies;
u32 rtos_poll_try_left;
};
static int drv8830_read_reg(struct i2c_client *client, u32 reg)
{
int rc;
rc = i2c_smbus_read_byte_data(client, reg);
if (rc < 0)
dev_err(&client->dev, "i2c reg read for 0x%x failed\n", reg);
return rc;
}
static int drv8830_write_reg(struct i2c_client *client, u32 reg, u8 val)
{
int rc;
rc = i2c_smbus_write_byte_data(client, reg, val);
if (rc < 0)
dev_err(&client->dev, "i2c reg write for 0x%xfailed\n", reg);
return rc;
}
static int drv8830_detect(struct drv8830_data *data)
{
int rc;
struct i2c_client *client = data->client;
/* Keep standby mode, but write voltage setting to control register */
rc = drv8830_write_reg(client, DRV8830_CONTROL_REG,
(data->vset << DRV8830_VSET_OFFSET));
if (rc < 0)
return rc;
/* Read back voltage setting from control register */
rc = drv8830_read_reg(client,
DRV8830_CONTROL_REG) >> DRV8830_VSET_OFFSET;
if (rc != data->vset)
return -ENODEV;
return 0;
}
static void drv8830_do_open(struct drv8830_data *data)
{
struct i2c_client *client = data->client;
u8 polarity = data->polarity;
if (polarity) {
drv8830_write_reg(client, DRV8830_CONTROL_REG,
(data->vset << DRV8830_VSET_OFFSET)
| DRV8830_IN0);
} else {
drv8830_write_reg(client, DRV8830_CONTROL_REG,
(data->vset << DRV8830_VSET_OFFSET)
| DRV8830_IN1);
}
}
static void drv8830_do_close(struct drv8830_data *data)
{
struct i2c_client *client = data->client;
u8 polarity = data->polarity;
if (polarity) {
drv8830_write_reg(client, DRV8830_CONTROL_REG,
(data->vset << DRV8830_VSET_OFFSET) | DRV8830_IN1);
} else {
drv8830_write_reg(client, DRV8830_CONTROL_REG,
(data->vset << DRV8830_VSET_OFFSET) | DRV8830_IN0);
}
}
static int drv8830_pulse_motor(struct drv8830_data *data)
{
int rc;
struct i2c_client *client = data->client;
if (data->open_target)
drv8830_do_open(data);
else
drv8830_do_close(data);
msleep(PULSE_DURATION_MS);
/* Brake the motor so the cut filter doesn't move */
rc = drv8830_write_reg(client, DRV8830_CONTROL_REG, DRV8830_BRAKE);
data->open_state = data->open_target;
if (!data->initialized) {
data->initialized = 1;
dev_info(&client->dev, "DRV8830 state initialized\n");
}
return rc;
}
static void drv8830_worker(struct work_struct *work)
{
struct drv8830_data *data =
container_of(work, struct drv8830_data, pulse_work);
drv8830_pulse_motor(data);
}
static ssize_t drv8830_show_open(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct drv8830_data *data = i2c_get_clientdata(to_i2c_client(dev));
if (!data->probed) {
dev_err(dev, "sysfs entry accessed before actual probe\n");
return -EAGAIN;
}
if (!data->initialized)
return sprintf(buf, "Uninitialized\n");
return sprintf(buf, "%u\n", data->open_state);
}
static ssize_t drv8830_store_open(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct drv8830_data *data = i2c_get_clientdata(client);
int ret;
if (!data->probed) {
dev_err(dev, "sysfs entry accessed before actual probe\n");
return -EAGAIN;
}
ret = kstrtou8(buf, 8, &data->open_target);
if (ret < 0)
return ret;
cancel_work_sync(&data->pulse_work);
schedule_work(&data->pulse_work);
return count;
}
static DEVICE_ATTR(open,
0644,
drv8830_show_open,
drv8830_store_open);
static struct attribute *drv8830_attributes[] = {
&dev_attr_open.attr,
NULL
};
static const struct attribute_group drv8830_attr_group = {
.attrs = drv8830_attributes,
};
static int drv8830_parse_dt(struct device *dev, struct drv8830_pdata *pdata)
{
u32 value;
int rc = 0;
/* Parse mandatory properties */
rc = of_property_read_u32(dev->of_node, "ti,vset_setting", &value);
if (rc < 0) {
pdata->vset = 0;
return rc;
}
/* vset must be 6 bits or less */
pdata->vset = (u8) (value & GENMASK(5, 0));
rc = of_property_read_u32(dev->of_node, "ti,polarity", &value);
if (rc < 0) {
pdata->polarity = 0;
return rc;
}
pdata->polarity = (u8) value;
pdata->fault_gpio = of_get_named_gpio(dev->of_node, "ti,fault_gpio", 0);
if (!gpio_is_valid(pdata->fault_gpio)) {
dev_err(dev,
"%s - Error: Could not get named gpio! Error code: %d\n",
__func__, pdata->fault_gpio);
return pdata->fault_gpio;
}
/* Parse optional properties */
rc = of_property_read_u32(dev->of_node, "ti,default_state", &value);
if (rc < 0)
pdata->default_state = 0;
else
pdata->default_state = (u8) value;
if (of_find_property(dev->of_node, "ti,skip_init", NULL)) {
pdata->skip_init = 1;
dev_info(dev, "skip_init is set for DRV8830\n");
} else {
pdata->skip_init = 0;
}
#ifdef CONFIG_TI_DRV8830_QUERY_RTOS
if (of_property_read_bool(dev->of_node, "ti,rtos_probe_after_finish"))
pdata->rtos_probe_after_finish = 1;
else
pdata->rtos_probe_after_finish = 0;
rc = of_property_read_u32(dev->of_node, "ti,rtos_poll_interval_ms",
&pdata->rtos_poll_interval_ms);
if (rc < 0)
pdata->rtos_poll_interval_ms = DEFAULT_RTOS_POLL_INTERVAL_MS;
rc = of_property_read_u32(dev->of_node, "ti,rtos_poll_try_number",
&pdata->rtos_poll_try_number);
if (rc < 0)
pdata->rtos_poll_try_number = DEFAULT_RTOS_POLL_TRY_NUMBER;
// Handle bad input
if (pdata->rtos_poll_try_number == 0)
pdata->rtos_poll_try_number = 1;
#endif
return 0;
}
static irqreturn_t drv8830_irq(int irq, void *data)
{
struct drv8830_data *drv_data = data;
struct i2c_client *client = drv_data->client;
u8 fault_reg;
fault_reg = drv8830_read_reg(drv_data->client, DRV8830_FAULT_REG);
if ((fault_reg & DRV8830_FAULT) == 0x0)
return IRQ_NONE;
if (fault_reg & DRV8830_ILIMIT)
dev_crit(&client->dev, "FAULT: extended current limit event\n");
else if (fault_reg & DRV8830_OTS)
dev_crit(&client->dev, "FAULT: overtemperature (OTS) condition\n");
else if (fault_reg & DRV8830_UVLO)
dev_crit(&client->dev, "FAULT: undervoltage lockout\n");
else if (fault_reg & DRV8830_OCP)
dev_crit(&client->dev, "FAULT: overcurrent (OCP) event\n");
else
dev_crit(&client->dev, "Fault bit set but no specific fault\n");
drv8830_write_reg(client, DRV8830_FAULT_REG, fault_reg | DRV8830_CLEAR);
return IRQ_HANDLED;
}
static int drv8830_probe_i2c(struct drv8830_data *data)
{
struct i2c_client *client = data->client;
int rc;
if (!client)
return -EIO;
rc = drv8830_detect(data);
if (rc < 0) {
dev_info(&client->dev,
"drv8830 is not detected on i2c address 0x%02X!\n",
client->addr);
return rc;
}
rc = devm_request_threaded_irq(&client->dev,
gpio_to_irq(data->fault_gpio), NULL,
drv8830_irq,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
"drv8830", data);
if (rc < 0) {
dev_err(&client->dev, "can't request fault_gpio irq\n");
return rc;
}
INIT_WORK(&data->pulse_work, drv8830_worker);
if (!data->skip_init) {
dev_info(&client->dev, "Setting DRV8830 to default state\n");
data->open_target = data->default_state;
schedule_work(&data->pulse_work);
}
data->probed = 1;
dev_info(&client->dev, "probe completed");
return 0;
}
#ifdef CONFIG_TI_DRV8830_QUERY_RTOS
static void drv8830_poll_rtos(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct drv8830_data *data =
container_of(dwork, struct drv8830_data, poll_rtos_work);
int rc;
if (freertos_cpu1_exited()) {
dev_info(&data->client->dev,
"Detected freertos exit, probing drv8830\n");
rc = drv8830_probe_i2c(data);
if (rc < 0)
dev_err(&data->client->dev, "Failed to probe\n");
return;
}
if (--data->rtos_poll_try_left > 0) {
schedule_delayed_work(dwork, data->rtos_poll_interval_jiffies);
return;
}
dev_err(&data->client->dev,
"Exceeded max number of tries to poll rtos\n");
}
#endif
static int drv8830_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct drv8830_data *data;
struct drv8830_pdata *pdata;
int rc;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "i2c is not supported\n");
return -EIO;
}
if (client->dev.of_node) {
pdata = devm_kzalloc(&client->dev,
sizeof(struct drv8830_pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
/* parse DT */
rc = drv8830_parse_dt(&client->dev, pdata);
if (rc) {
dev_err(&client->dev, "DT parsing failed\n");
return rc;
}
} else {
pdata = client->dev.platform_data;
if (!pdata) {
dev_err(&client->dev, "invalid data\n");
return -EINVAL;
}
}
data = devm_kzalloc(&client->dev, sizeof(struct drv8830_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
data->vset = pdata->vset;
data->polarity = pdata->polarity;
data->fault_gpio = pdata->fault_gpio;
data->skip_init = pdata->skip_init;
data->default_state = pdata->default_state;
data->open_target = 0;
data->open_state = 0;
data->initialized = 0;
data->probed = 0;
i2c_set_clientdata(client, data);
data->client = client;
rc = sysfs_create_group(&client->dev.kobj, &drv8830_attr_group);
if (rc < 0) {
dev_err(&client->dev, "can't create sysfs group\n");
return rc;
}
#ifdef CONFIG_TI_DRV8830_QUERY_RTOS
if (pdata->rtos_probe_after_finish) {
dev_info(&client->dev,
"Waiting rtos to finish to probe drv8830\n");
data->rtos_poll_interval_jiffies =
msecs_to_jiffies(pdata->rtos_poll_interval_ms);
data->rtos_poll_try_left = pdata->rtos_poll_try_number;
INIT_DELAYED_WORK(&data->poll_rtos_work, drv8830_poll_rtos);
schedule_delayed_work(&data->poll_rtos_work, 0);
return 0;
}
#endif
return drv8830_probe_i2c(data);
}
static int drv8830_remove(struct i2c_client *client)
{
struct drv8830_data *data = i2c_get_clientdata(client);
#ifdef CONFIG_TI_DRV8830_QUERY_RTOS
cancel_delayed_work_sync(&data->poll_rtos_work);
#endif
cancel_work_sync(&data->pulse_work);
return 0;
}
static const struct i2c_device_id drv8830_id_table[] = {
{"drv8830", 0},
{ },
};
MODULE_DEVICE_TABLE(i2c, drv8830_id_table);
#ifdef CONFIG_OF
static const struct of_device_id drv8830_of_id_table[] = {
{.compatible = "ti, drv8830"},
{ },
};
#else
#define drv8830_of_id_table NULL
#endif
static struct i2c_driver drv8830_i2c_driver = {
.driver = {
.name = "drv8830",
.owner = THIS_MODULE,
.of_match_table = drv8830_of_id_table,
},
.probe = drv8830_probe,
.remove = drv8830_remove,
.id_table = drv8830_id_table,
};
module_i2c_driver(drv8830_i2c_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("TI DRV8830 chip driver");