Files
kernel_xiaomi_sm8250/drivers/misc/drv8846.c
Sebastiano Barezzi f515aa72bb drivers: misc: drv8846: Remove references to lpm_disable_for_dev
Change-Id: Ia8160e922cfa338b79182f4278fd122eb2721f37
2023-03-05 15:28:03 +01:00

810 lines
20 KiB
C

/* drivers/misc/drv8846.c - drv8846 step motor soc driver
*
* Copyright (c) 2014-2015, Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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 DEBUG
#define pr_fmt(fmt) "drv8846: %s: %d " fmt, __func__, __LINE__
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/freezer.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/regulator/consumer.h>
#include <linux/of_gpio.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/pwm.h>
#include <linux/mm.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/hrtimer.h>
#include <linux/debugfs.h>
#include <linux/drv8846.h>
static DECLARE_WAIT_QUEUE_HEAD(poll_wait_queue);
struct pwm_setting {
u64 pre_period_ns;
u64 period_ns;
u64 duty_ns;
};
struct drv8846_soc_ctrl {
dev_t dev_num;
struct cdev c_cdev;
struct class *chr_class;
struct device *chr_dev;
struct platform_device *pdev;
struct device_node *of_node;
struct pinctrl *pinctrl;
struct pinctrl_state *pinctrl_default;
struct pwm_device *pwm_dev;
struct pwm_setting pwm_setting;
struct hrtimer pwm_timer;
struct work_struct pwm_apply_work;
struct drv8846_private_data pdata;
atomic_t move_done;
struct mutex motor_mutex;
uint32_t step_mode;
uint32_t direction;
int gpio_mode0;
int gpio_mode1;
int gpio_dir;
int gpio_sleep;
int gpio_pwren;
enum running_state state;
};
static int __drv8846_config_pwm(struct drv8846_soc_ctrl *c_ctrl,
struct pwm_setting *pwm)
{
int rc;
struct pwm_state pstate;
pwm_get_state(c_ctrl->pwm_dev, &pstate);
pstate.enabled = !!(pwm->duty_ns != 0);
pstate.period = pwm->period_ns;
pstate.duty_cycle = pwm->duty_ns;
pstate.output_type = PWM_OUTPUT_FIXED;
pstate.output_pattern = NULL;
pr_debug("enable %d\n", pstate.enabled);
rc = pwm_apply_state(c_ctrl->pwm_dev, &pstate);
if (rc < 0)
pr_err("Apply PWM state failed, rc=%d\n", rc);
if (pstate.enabled == false) {
gpio_direction_output(c_ctrl->gpio_sleep, 0);
atomic_set(&c_ctrl->move_done, 1);
wake_up(&poll_wait_queue);
} else {
gpio_direction_output(c_ctrl->gpio_sleep, 1);
}
return rc;
}
static void pwm_config_work(struct work_struct *work)
{
struct drv8846_soc_ctrl *c_ctrl =
container_of(work, struct drv8846_soc_ctrl, pwm_apply_work);
struct pwm_setting setting;
setting = c_ctrl->pwm_setting;
__drv8846_config_pwm(c_ctrl, &setting);
}
static enum hrtimer_restart pwm_hrtimer_handler(struct hrtimer *timer)
{
struct drv8846_soc_ctrl *c_ctrl =
container_of(timer, struct drv8846_soc_ctrl, pwm_timer);
switch (c_ctrl->state) {
case SPEEDUP: {
c_ctrl->state = SLOWDOWN;
c_ctrl->pwm_setting.period_ns = c_ctrl->pdata.slow_period;
c_ctrl->pwm_setting.pre_period_ns = c_ctrl->pdata.slow_period;
c_ctrl->pwm_setting.duty_ns = c_ctrl->pdata.slow_period >> 1;
hrtimer_forward_now(
&c_ctrl->pwm_timer,
ktime_set(c_ctrl->pdata.slow_duration / MSEC_PER_SEC,
(c_ctrl->pdata.slow_duration % MSEC_PER_SEC) *
NSEC_PER_MSEC));
break;
}
case SLOWDOWN:
case STILL:
default: {
c_ctrl->state = STOP;
c_ctrl->pwm_setting.duty_ns = DUTY_DEFAULT;
break;
}
}
schedule_work(&c_ctrl->pwm_apply_work);
if (c_ctrl->state == STOP)
return HRTIMER_NORESTART;
else
return HRTIMER_RESTART;
}
void drv8846_move(struct drv8846_soc_ctrl *c_ctrl)
{
pr_info("move %s\n", (c_ctrl->pdata.dir ? "up" : "down"));
c_ctrl->direction = c_ctrl->pdata.dir;
gpio_direction_output(c_ctrl->gpio_dir,
((c_ctrl->pdata.dir == UP) ? 0 : 1));
gpio_direction_output(c_ctrl->gpio_sleep, 1);
c_ctrl->pwm_setting.period_ns = c_ctrl->pdata.speed_period;
c_ctrl->pwm_setting.pre_period_ns = c_ctrl->pdata.speed_period;
c_ctrl->pwm_setting.duty_ns = c_ctrl->pdata.speed_period >> 1;
hrtimer_start(&c_ctrl->pwm_timer,
ktime_set(c_ctrl->pdata.speed_duration / MSEC_PER_SEC,
(c_ctrl->pdata.speed_duration % MSEC_PER_SEC) *
NSEC_PER_MSEC),
HRTIMER_MODE_REL);
schedule_work(&c_ctrl->pwm_apply_work);
return;
}
static unsigned int drv8846_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct drv8846_soc_ctrl *c_ctrl = filp->private_data;
pr_debug("Poll enter\n");
poll_wait(filp, &poll_wait_queue, wait);
if (atomic_read(&c_ctrl->move_done)) {
atomic_set(&c_ctrl->move_done, 0);
mask = POLLIN | POLLRDNORM;
}
return mask;
}
static ssize_t drv8846_debug_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct drv8846_soc_ctrl *c_ctrl = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "period:%d, duration:%d\n",
c_ctrl->pdata.speed_period,
c_ctrl->pdata.speed_duration);
}
static ssize_t drv8846_debug_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int enable;
int ret = 0;
struct drv8846_soc_ctrl *c_ctrl = dev_get_drvdata(dev);
ret = sscanf(buf, "%d", &enable);
if (0 == ret)
pr_err("Input %d\n", enable);
c_ctrl->pdata.speed_period = 52083;
c_ctrl->pdata.speed_duration = 550;
if (99 == enable) {
c_ctrl->pdata.dir = UP;
} else {
c_ctrl->pdata.dir = DOWN;
}
c_ctrl->state = STILL;
drv8846_move(c_ctrl);
return count;
}
static DEVICE_ATTR(debug, S_IRUGO | S_IWUSR, drv8846_debug_show,
drv8846_debug_store);
static ssize_t drv8846_tuning_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct drv8846_soc_ctrl *c_ctrl = dev_get_drvdata(dev);
return snprintf(
buf, PAGE_SIZE,
"period:%d, duration:%d, slow_period = %d, slow_duration = %d\n",
c_ctrl->pdata.speed_period, c_ctrl->pdata.speed_duration,
c_ctrl->pdata.slow_period, c_ctrl->pdata.slow_duration);
}
static ssize_t drv8846_tuning_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int enable;
int ret = 0;
struct drv8846_soc_ctrl *c_ctrl = dev_get_drvdata(dev);
ret = sscanf(buf, "%d %d %d %d %d", &enable,
&c_ctrl->pdata.speed_period, &c_ctrl->pdata.speed_duration,
&c_ctrl->pdata.slow_period, &c_ctrl->pdata.slow_duration);
if (0 == ret)
pr_err("Input %d\n", enable);
pr_err("enable = %d, speed_period = %d, speed_duration = %d, slow_period = %d, slow_duration = %d\n",
enable, c_ctrl->pdata.speed_period, c_ctrl->pdata.speed_duration,
c_ctrl->pdata.slow_period, c_ctrl->pdata.slow_duration);
if (99 == enable) {
c_ctrl->pdata.dir = UP;
} else {
c_ctrl->pdata.dir = DOWN;
}
c_ctrl->state = SPEEDUP;
drv8846_move(c_ctrl);
return count;
}
static DEVICE_ATTR(tuning, S_IRUGO | S_IWUSR, drv8846_tuning_show,
drv8846_tuning_store);
static struct device_attribute *drv8846_attrs[] = {
&dev_attr_debug,
&dev_attr_tuning,
};
static long drv8846_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int rc = 0;
//ktime_t time_rem;
struct drv8846_soc_ctrl *c_ctrl = filp->private_data;
if (NULL == c_ctrl) {
pr_err("Private data is NULL\n");
return -EFAULT;
}
if (_IOC_TYPE(cmd) != MOTOR_IOC_MAGIC) {
pr_err("Magic number is worng\n");
return -ENODEV;
}
if (_IOC_DIR(cmd) & _IOC_READ)
rc = !access_ok(VERIFY_WRITE, (void __user *)arg,
_IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
rc = !access_ok(VERIFY_READ, (void __user *)arg,
_IOC_SIZE(cmd));
if (rc) {
pr_err("IOCTL cd access failed\n");
return -EFAULT;
}
if (_IOC_DIR(cmd) & _IOC_WRITE) {
memset(&c_ctrl->pdata, 0, sizeof(struct drv8846_private_data));
if (copy_from_user(&c_ctrl->pdata, (void __user *)arg,
sizeof(struct drv8846_private_data))) {
pr_err("Copy data from user space failed");
return -EFAULT;
}
}
switch (cmd) {
case MOTOR_IOC_SET_AUTO:
c_ctrl->state = SPEEDUP;
drv8846_move(c_ctrl);
break;
case MOTOR_IOC_SET_MANUAL:
c_ctrl->state = STILL;
drv8846_move(c_ctrl);
break;
case MOTOR_IOC_GET_REMAIN_TIME:
#if 0
if (hrtimer_active(&c_ctrl->pwm_timer)) {
time_rem = hrtimer_get_remaining(&c_ctrl->pwm_timer);
c_ctrl->pwm_time = (long)ktime_to_ms(time_rem);
}
#endif
break;
case MOTOR_IOC_GET_STATE:
c_ctrl->pdata.pwm_state = c_ctrl->state;
break;
default:
pr_warn("unsupport cmd:0x%x\n", cmd);
break;
}
if (_IOC_DIR(cmd) & _IOC_READ) {
if (copy_to_user((void __user *)arg, &c_ctrl->pdata,
sizeof(struct drv8846_private_data))) {
pr_err("Copy data to user space failed\n");
return -EFAULT;
}
}
return rc;
}
#ifdef CONFIG_COMPAT
static long drv8846_compat_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
return drv8846_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
}
#endif
static int drv8846_open(struct inode *inode, struct file *filp)
{
struct drv8846_soc_ctrl *c_ctrl = NULL;
c_ctrl = container_of(inode->i_cdev, struct drv8846_soc_ctrl, c_cdev);
filp->private_data = c_ctrl;
atomic_set(&c_ctrl->move_done, 0);
return 0;
}
static int drv8846_release(struct inode *inode, struct file *file)
{
struct drv8846_soc_ctrl *c_ctrl = file->private_data;
atomic_set(&c_ctrl->move_done, 0);
return 0;
}
static const struct file_operations drv8846_fops = {
.owner = THIS_MODULE,
.open = drv8846_open,
.release = drv8846_release,
.unlocked_ioctl = drv8846_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = drv8846_compat_ioctl,
#endif
.poll = drv8846_poll,
};
static int drv8846_pinctrl_init(struct drv8846_soc_ctrl *c_ctrl)
{
int rc = 0;
/* Get pinctrl if target uses pinctrl */
c_ctrl->pinctrl = devm_pinctrl_get(&c_ctrl->pdev->dev);
if (IS_ERR_OR_NULL(c_ctrl->pinctrl)) {
rc = PTR_ERR(c_ctrl->pinctrl);
pr_err("Target does not use pinctrl %d\n", rc);
goto get_pinctrl_err;
}
c_ctrl->pinctrl_default =
pinctrl_lookup_state(c_ctrl->pinctrl, "default");
if (IS_ERR_OR_NULL(c_ctrl->pinctrl_default)) {
rc = PTR_ERR(c_ctrl->pinctrl_default);
pr_err("Can not lookup default pinstate %d\n", rc);
goto lookup_pinctrl_err;
}
return 0;
lookup_pinctrl_err:
devm_pinctrl_put(c_ctrl->pinctrl);
get_pinctrl_err:
c_ctrl->pinctrl = NULL;
return rc;
}
static int drv8846_gpio_config(struct drv8846_soc_ctrl *c_ctrl)
{
int32_t rc = 0;
rc = gpio_request_one(c_ctrl->gpio_mode0, GPIOF_OUT_INIT_HIGH,
"motor-mode0");
if (rc < 0) {
pr_err("Failed to request mode0 GPIO %d\n", c_ctrl->gpio_mode0);
goto mode0_gpio_req_err;
}
gpio_direction_output(c_ctrl->gpio_mode0, (c_ctrl->step_mode & 0x01));
rc = gpio_request_one(c_ctrl->gpio_mode1, GPIOF_OUT_INIT_HIGH,
"motor-mode1");
if (rc < 0) {
pr_err("Failed to request mode1 GPIO %d\n", c_ctrl->gpio_mode1);
goto mode1_gpio_req_err;
}
gpio_direction_output(c_ctrl->gpio_mode1, (c_ctrl->step_mode & 0x02));
rc = gpio_request_one(c_ctrl->gpio_dir, GPIOF_OUT_INIT_HIGH,
"motor-dir");
if (rc < 0) {
pr_err("Failed to request direction GPIO %d\n",
c_ctrl->gpio_dir);
goto dir_gpio_req_err;
}
gpio_direction_output(c_ctrl->gpio_dir, 0);
rc = gpio_request_one(c_ctrl->gpio_sleep, GPIOF_OUT_INIT_LOW,
"motor-sleep");
if (rc < 0) {
pr_err("Failed to request sleep GPIO %d\n", c_ctrl->gpio_sleep);
goto sleep_gpio_req_err;
}
gpio_direction_output(c_ctrl->gpio_sleep, 0);
rc = gpio_request_one(c_ctrl->gpio_pwren, GPIOF_OUT_INIT_HIGH,
"motor-pwr");
if (rc < 0) {
pr_err("Failed to request power enable GPIO %d\n",
c_ctrl->gpio_pwren);
goto pwren_gpio_req_err;
}
gpio_direction_output(c_ctrl->gpio_pwren, 1);
return 0;
pwren_gpio_req_err:
if (gpio_is_valid(c_ctrl->gpio_sleep))
gpio_free(c_ctrl->gpio_sleep);
sleep_gpio_req_err:
if (gpio_is_valid(c_ctrl->gpio_dir))
gpio_free(c_ctrl->gpio_dir);
dir_gpio_req_err:
if (gpio_is_valid(c_ctrl->gpio_mode1)) {
gpio_direction_output(c_ctrl->gpio_mode1, 0);
gpio_free(c_ctrl->gpio_mode1);
}
mode1_gpio_req_err:
if (gpio_is_valid(c_ctrl->gpio_mode0)) {
gpio_direction_output(c_ctrl->gpio_mode0, 0);
gpio_free(c_ctrl->gpio_mode0);
}
mode0_gpio_req_err:
return rc;
}
int drv8846_parse_dt(struct drv8846_soc_ctrl *c_ctrl)
{
int rc = 0;
struct device_node *of_node = NULL;
of_node = c_ctrl->pdev->dev.of_node;
c_ctrl->pwm_setting.duty_ns = DUTY_DEFAULT;
c_ctrl->pwm_setting.period_ns = PERIOD_DEFAULT;
c_ctrl->gpio_mode0 =
of_get_named_gpio_flags(of_node, "motor,gpio-mode0", 0, NULL);
if (!gpio_is_valid(c_ctrl->gpio_mode0)) {
pr_err("Gpio mode0 pin %d is invalid.", c_ctrl->gpio_mode0);
goto parse_gpio_err;
}
c_ctrl->gpio_mode1 =
of_get_named_gpio_flags(of_node, "motor,gpio-mode1", 0, NULL);
if (!gpio_is_valid(c_ctrl->gpio_mode1)) {
pr_err("Gpio mode1 pin %d is invalid.", c_ctrl->gpio_mode1);
goto parse_gpio_err;
}
c_ctrl->gpio_sleep =
of_get_named_gpio_flags(of_node, "motor,gpio-sleep", 0, NULL);
if (!gpio_is_valid(c_ctrl->gpio_sleep)) {
pr_err("Gpio sleep pin %d is invalid.", c_ctrl->gpio_sleep);
goto parse_gpio_err;
}
c_ctrl->gpio_dir =
of_get_named_gpio_flags(of_node, "motor,gpio-dir", 0, NULL);
if (!gpio_is_valid(c_ctrl->gpio_dir)) {
pr_err("Gpio direction pin %d is invalid.", c_ctrl->gpio_dir);
goto parse_gpio_err;
}
c_ctrl->gpio_pwren =
of_get_named_gpio_flags(of_node, "motor,gpio-pwren", 0, NULL);
if (!gpio_is_valid(c_ctrl->gpio_pwren)) {
pr_err("Gpio power enable pin %d is invalid.",
c_ctrl->gpio_pwren);
goto parse_gpio_err;
}
rc = of_property_read_u32(of_node, "motor,step-mode",
&c_ctrl->step_mode);
if (rc < 0) {
pr_err("Gpio step mode pin %d not set, use default.",
DEFAULT_STEP_MODE);
c_ctrl->step_mode = DEFAULT_STEP_MODE;
}
pr_err("gpio-dir = %d\n", c_ctrl->gpio_dir);
return 0;
parse_gpio_err:
return -EINVAL;
}
static int drv8846_probe(struct platform_device *pdev)
{
int32_t rc = 0;
struct drv8846_soc_ctrl *c_ctrl = NULL;
pr_info("Enter");
if (!pdev->dev.of_node) {
pr_err("of_node NULL");
return -EINVAL;
}
c_ctrl = kzalloc(sizeof(struct drv8846_soc_ctrl), GFP_KERNEL);
if (!c_ctrl)
return -ENOMEM;
c_ctrl->pdev = pdev;
platform_set_drvdata(pdev, c_ctrl);
pr_debug("Start parse device tree");
if (pdev->dev.of_node) {
rc = drv8846_parse_dt(c_ctrl);
if (rc < 0) {
pr_err("Parse dt failed");
goto parse_dt_err;
}
}
rc = drv8846_pinctrl_init(c_ctrl);
if (rc) {
pr_err("Failed to init pinctrl\n");
goto init_pinctrl_err;
} else {
if (c_ctrl->pinctrl) {
rc = pinctrl_select_state(c_ctrl->pinctrl,
c_ctrl->pinctrl_default);
if (rc < 0) {
pr_err("Failed to select default pinstate %d\n",
rc);
goto select_pinctrl_err;
}
}
}
rc = drv8846_gpio_config(c_ctrl);
if (rc < 0) {
pr_err("Failed to config gpio\n");
goto select_pinctrl_err;
}
c_ctrl->pwm_dev = devm_of_pwm_get(&c_ctrl->pdev->dev,
c_ctrl->pdev->dev.of_node, NULL);
if (IS_ERR(c_ctrl->pwm_dev)) {
rc = PTR_ERR(c_ctrl->pwm_dev);
if (rc != -EPROBE_DEFER)
pr_err("Get pwm device for motor failed, rc=%d\n", rc);
goto get_pwm_device_err;
}
mutex_init(&c_ctrl->motor_mutex);
INIT_WORK(&c_ctrl->pwm_apply_work, pwm_config_work);
hrtimer_init(&c_ctrl->pwm_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
c_ctrl->pwm_timer.function = pwm_hrtimer_handler;
c_ctrl->chr_class = class_create(THIS_MODULE, DRV8846_CLASS_NAME);
if (c_ctrl->chr_class == NULL) {
pr_err("Failed to create class\n");
rc = -ENODEV;
goto create_class_err;
}
rc = alloc_chrdev_region(&c_ctrl->dev_num, 0, 1, DRV8846_DRV_NAME);
if (rc < 0) {
pr_err("Failed to allocate chrdev region\n");
goto alloc_dev_err;
}
c_ctrl->chr_dev =
device_create(c_ctrl->chr_class, NULL, c_ctrl->dev_num, c_ctrl,
DRV8846_DRV_NAME);
if (IS_ERR(c_ctrl->chr_dev)) {
pr_err("Failed to create char device\n");
rc = PTR_ERR(c_ctrl->chr_dev);
goto create_device_err;
}
cdev_init(&(c_ctrl->c_cdev), &drv8846_fops);
c_ctrl->c_cdev.owner = THIS_MODULE;
rc = cdev_add(&(c_ctrl->c_cdev), c_ctrl->dev_num, 1);
if (rc < 0) {
pr_err("Failed to add cdev\n");
goto add_cdev_err;
}
rc = device_create_file(c_ctrl->chr_dev, drv8846_attrs[0]);
if (rc < 0)
pr_err("Failed to create debug file: %d\n", rc);
rc = device_create_file(c_ctrl->chr_dev, drv8846_attrs[1]);
if (rc < 0)
pr_err("Failed to create debug file: %d\n", rc);
pr_debug("Successfully probed\n");
return 0;
add_cdev_err:
if (c_ctrl->chr_dev)
device_destroy(c_ctrl->chr_class, c_ctrl->dev_num);
create_device_err:
unregister_chrdev_region(c_ctrl->dev_num, 1);
alloc_dev_err:
if (c_ctrl->chr_class)
class_destroy(c_ctrl->chr_class);
create_class_err:
cancel_work_sync(&c_ctrl->pwm_apply_work);
hrtimer_cancel(&c_ctrl->pwm_timer);
mutex_destroy(&c_ctrl->motor_mutex);
devm_pwm_put(&c_ctrl->pdev->dev, c_ctrl->pwm_dev);
get_pwm_device_err:
if (gpio_is_valid(c_ctrl->gpio_pwren)) {
gpio_direction_output(c_ctrl->gpio_pwren, 0);
gpio_free(c_ctrl->gpio_pwren);
}
if (gpio_is_valid(c_ctrl->gpio_sleep))
gpio_free(c_ctrl->gpio_sleep);
if (gpio_is_valid(c_ctrl->gpio_dir))
gpio_free(c_ctrl->gpio_dir);
if (gpio_is_valid(c_ctrl->gpio_mode1)) {
gpio_direction_output(c_ctrl->gpio_mode1, 0);
gpio_free(c_ctrl->gpio_mode1);
}
if (gpio_is_valid(c_ctrl->gpio_mode0)) {
gpio_direction_output(c_ctrl->gpio_mode0, 0);
gpio_free(c_ctrl->gpio_mode0);
}
select_pinctrl_err:
if (c_ctrl->pinctrl) {
devm_pinctrl_put(c_ctrl->pinctrl);
c_ctrl->pinctrl = NULL;
}
init_pinctrl_err:
parse_dt_err:
platform_set_drvdata(pdev, NULL);
kfree(c_ctrl);
return rc;
}
static int drv8846_remove(struct platform_device *pdev)
{
struct drv8846_soc_ctrl *c_ctrl = platform_get_drvdata(pdev);
if (c_ctrl->chr_dev)
device_destroy(c_ctrl->chr_class, c_ctrl->dev_num);
unregister_chrdev_region(c_ctrl->dev_num, 1);
if (c_ctrl->chr_class)
class_destroy(c_ctrl->chr_class);
cancel_work_sync(&c_ctrl->pwm_apply_work);
hrtimer_cancel(&c_ctrl->pwm_timer);
mutex_destroy(&c_ctrl->motor_mutex);
devm_pwm_put(&c_ctrl->pdev->dev, c_ctrl->pwm_dev);
if (gpio_is_valid(c_ctrl->gpio_pwren)) {
gpio_direction_output(c_ctrl->gpio_pwren, 0);
gpio_free(c_ctrl->gpio_pwren);
}
if (gpio_is_valid(c_ctrl->gpio_sleep))
gpio_free(c_ctrl->gpio_sleep);
if (gpio_is_valid(c_ctrl->gpio_dir))
gpio_free(c_ctrl->gpio_dir);
if (gpio_is_valid(c_ctrl->gpio_mode1)) {
gpio_direction_output(c_ctrl->gpio_mode1, 0);
gpio_free(c_ctrl->gpio_mode1);
}
if (gpio_is_valid(c_ctrl->gpio_mode0)) {
gpio_direction_output(c_ctrl->gpio_mode0, 0);
gpio_free(c_ctrl->gpio_mode0);
}
if (c_ctrl->pinctrl) {
devm_pinctrl_put(c_ctrl->pinctrl);
c_ctrl->pinctrl = NULL;
}
platform_set_drvdata(pdev, NULL);
kfree(c_ctrl);
return 0;
}
static int drv8846_suspend(struct device *dev)
{
struct drv8846_soc_ctrl *c_ctrl = dev_get_drvdata(dev);
return gpio_direction_output(c_ctrl->gpio_pwren, 0);
}
static int drv8846_resume(struct device *dev)
{
struct drv8846_soc_ctrl *c_ctrl = dev_get_drvdata(dev);
return gpio_direction_output(c_ctrl->gpio_pwren, 1);
}
static const struct dev_pm_ops drv8846_pm_ops = {
.suspend = drv8846_suspend,
.resume = drv8846_resume,
};
static const struct of_device_id drv8846_match_table[] = {
{ .compatible = DRV8846_DEV_NAME },
{}
};
MODULE_DEVICE_TABLE(of, drv8846_match_table);
static struct platform_driver drv8846_driver = {
.driver = {
.name = DRV8846_DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(drv8846_match_table),
.pm = &drv8846_pm_ops,
},
.probe = drv8846_probe,
.remove = drv8846_remove,
};
module_platform_driver(drv8846_driver);
MODULE_DESCRIPTION("TI Dual H-Bridge Stepper Motor Driver");
MODULE_AUTHOR("Zhu Nengjin <zhunengjin@xiaomi.com>");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("2.0");