| /* 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"); |