leds: qpnp: Add snapshot of vibrator LDO driver
This is a snapshot of the vibrator LDO driver as of msm-4.19 'commit c527cd5c987ac (Merge "mhi: cntrl: qcom: allow printing large strings to IPC logs")' with the following modifications: - Replace snprintf with scnprintf. Change-Id: Id58fbcc33607c309366104fe72282c09c86a3227 Signed-off-by: Jishnu Prakash <jprakash@codeaurora.org>
This commit is contained in:
parent
058b861752
commit
108427ae28
@ -850,6 +850,14 @@ config LEDS_QTI_TRI_LED
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called leds-qti-tri-led.
|
||||
|
||||
config LEDS_QPNP_VIBRATOR_LDO
|
||||
tristate "Vibrator-LDO support for QPNP PMIC"
|
||||
depends on LEDS_CLASS && MFD_SPMI_PMIC
|
||||
help
|
||||
This option enables device driver support for the vibrator-ldo
|
||||
peripheral found on Qualcomm Technologies, Inc. QPNP PMICs.
|
||||
The vibrator-ldo peripheral is capable of driving ERM vibrators.
|
||||
|
||||
comment "LED Triggers"
|
||||
source "drivers/leds/trigger/Kconfig"
|
||||
|
||||
|
@ -87,6 +87,7 @@ obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o
|
||||
obj-$(CONFIG_LEDS_LM3697) += leds-lm3697.o
|
||||
obj-$(CONFIG_LEDS_LM36274) += leds-lm36274.o
|
||||
obj-$(CONFIG_LEDS_QTI_TRI_LED) += leds-qti-tri-led.o
|
||||
obj-$(CONFIG_LEDS_QPNP_VIBRATOR_LDO) += leds-qpnp-vibrator-ldo.o
|
||||
|
||||
# LED SPI Drivers
|
||||
obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o
|
||||
|
553
drivers/leds/leds-qpnp-vibrator-ldo.c
Normal file
553
drivers/leds/leds-qpnp-vibrator-ldo.c
Normal file
@ -0,0 +1,553 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. */
|
||||
|
||||
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
/* Vibrator-LDO register definitions */
|
||||
#define QPNP_VIB_LDO_REG_STATUS1 0x08
|
||||
#define QPNP_VIB_LDO_VREG_READY BIT(7)
|
||||
|
||||
#define QPNP_VIB_LDO_REG_VSET_LB 0x40
|
||||
|
||||
#define QPNP_VIB_LDO_REG_EN_CTL 0x46
|
||||
#define QPNP_VIB_LDO_EN BIT(7)
|
||||
|
||||
/* Vibrator-LDO voltage settings */
|
||||
#define QPNP_VIB_LDO_VMIN_UV 1504000
|
||||
#define QPNP_VIB_LDO_VMAX_UV 3544000
|
||||
#define QPNP_VIB_LDO_VOLT_STEP_UV 8000
|
||||
|
||||
/*
|
||||
* Define vibration periods: default(5sec), min(50ms), max(15sec) and
|
||||
* overdrive(30ms).
|
||||
*/
|
||||
#define QPNP_VIB_MIN_PLAY_MS 50
|
||||
#define QPNP_VIB_PLAY_MS 5000
|
||||
#define QPNP_VIB_MAX_PLAY_MS 15000
|
||||
#define QPNP_VIB_OVERDRIVE_PLAY_MS 30
|
||||
|
||||
struct vib_ldo_chip {
|
||||
struct led_classdev cdev;
|
||||
struct regmap *regmap;
|
||||
struct mutex lock;
|
||||
struct hrtimer stop_timer;
|
||||
struct hrtimer overdrive_timer;
|
||||
struct work_struct vib_work;
|
||||
struct work_struct overdrive_work;
|
||||
|
||||
u16 base;
|
||||
int vmax_uV;
|
||||
int overdrive_volt_uV;
|
||||
int ldo_uV;
|
||||
int state;
|
||||
u64 vib_play_ms;
|
||||
bool vib_enabled;
|
||||
bool disable_overdrive;
|
||||
};
|
||||
|
||||
static inline int qpnp_vib_ldo_poll_status(struct vib_ldo_chip *chip)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(chip->regmap,
|
||||
chip->base + QPNP_VIB_LDO_REG_STATUS1, val,
|
||||
val & QPNP_VIB_LDO_VREG_READY, 100, 1000);
|
||||
if (ret < 0) {
|
||||
pr_err("Vibrator LDO vreg_ready timeout, status=0x%02x, ret=%d\n",
|
||||
val, ret);
|
||||
|
||||
/* Keep VIB_LDO disabled */
|
||||
regmap_update_bits(chip->regmap,
|
||||
chip->base + QPNP_VIB_LDO_REG_EN_CTL,
|
||||
QPNP_VIB_LDO_EN, 0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qpnp_vib_ldo_set_voltage(struct vib_ldo_chip *chip, int new_uV)
|
||||
{
|
||||
u32 vlevel;
|
||||
u8 reg[2];
|
||||
int ret;
|
||||
|
||||
if (chip->ldo_uV == new_uV)
|
||||
return 0;
|
||||
|
||||
vlevel = roundup(new_uV, QPNP_VIB_LDO_VOLT_STEP_UV) / 1000;
|
||||
reg[0] = vlevel & 0xff;
|
||||
reg[1] = (vlevel & 0xff00) >> 8;
|
||||
ret = regmap_bulk_write(chip->regmap,
|
||||
chip->base + QPNP_VIB_LDO_REG_VSET_LB, reg, 2);
|
||||
if (ret < 0) {
|
||||
pr_err("regmap write failed, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (chip->vib_enabled) {
|
||||
ret = qpnp_vib_ldo_poll_status(chip);
|
||||
if (ret < 0) {
|
||||
pr_err("Vibrator LDO status polling timedout\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
chip->ldo_uV = new_uV;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int qpnp_vib_ldo_enable(struct vib_ldo_chip *chip, bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (chip->vib_enabled == enable)
|
||||
return 0;
|
||||
|
||||
ret = regmap_update_bits(chip->regmap,
|
||||
chip->base + QPNP_VIB_LDO_REG_EN_CTL,
|
||||
QPNP_VIB_LDO_EN,
|
||||
enable ? QPNP_VIB_LDO_EN : 0);
|
||||
if (ret < 0) {
|
||||
pr_err("Program Vibrator LDO %s is failed, ret=%d\n",
|
||||
enable ? "enable" : "disable", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
ret = qpnp_vib_ldo_poll_status(chip);
|
||||
if (ret < 0) {
|
||||
pr_err("Vibrator LDO status polling timedout\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
chip->vib_enabled = enable;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qpnp_vibrator_play_on(struct vib_ldo_chip *chip)
|
||||
{
|
||||
int volt_uV;
|
||||
int ret;
|
||||
|
||||
volt_uV = chip->vmax_uV;
|
||||
if (!chip->disable_overdrive)
|
||||
volt_uV = chip->overdrive_volt_uV ? chip->overdrive_volt_uV
|
||||
: min(chip->vmax_uV * 2, QPNP_VIB_LDO_VMAX_UV);
|
||||
|
||||
ret = qpnp_vib_ldo_set_voltage(chip, volt_uV);
|
||||
if (ret < 0) {
|
||||
pr_err("set voltage = %duV failed, ret=%d\n", volt_uV, ret);
|
||||
return ret;
|
||||
}
|
||||
pr_debug("voltage set to %d uV\n", volt_uV);
|
||||
|
||||
ret = qpnp_vib_ldo_enable(chip, true);
|
||||
if (ret < 0) {
|
||||
pr_err("vibration enable failed, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!chip->disable_overdrive)
|
||||
hrtimer_start(&chip->overdrive_timer,
|
||||
ms_to_ktime(QPNP_VIB_OVERDRIVE_PLAY_MS),
|
||||
HRTIMER_MODE_REL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void qpnp_vib_work(struct work_struct *work)
|
||||
{
|
||||
struct vib_ldo_chip *chip = container_of(work, struct vib_ldo_chip,
|
||||
vib_work);
|
||||
int ret = 0;
|
||||
|
||||
if (chip->state) {
|
||||
if (!chip->vib_enabled)
|
||||
ret = qpnp_vibrator_play_on(chip);
|
||||
|
||||
if (ret == 0)
|
||||
hrtimer_start(&chip->stop_timer,
|
||||
ms_to_ktime(chip->vib_play_ms),
|
||||
HRTIMER_MODE_REL);
|
||||
} else {
|
||||
if (!chip->disable_overdrive) {
|
||||
hrtimer_cancel(&chip->overdrive_timer);
|
||||
cancel_work_sync(&chip->overdrive_work);
|
||||
}
|
||||
qpnp_vib_ldo_enable(chip, false);
|
||||
}
|
||||
}
|
||||
|
||||
static enum hrtimer_restart vib_stop_timer(struct hrtimer *timer)
|
||||
{
|
||||
struct vib_ldo_chip *chip = container_of(timer, struct vib_ldo_chip,
|
||||
stop_timer);
|
||||
|
||||
chip->state = 0;
|
||||
schedule_work(&chip->vib_work);
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static void qpnp_vib_overdrive_work(struct work_struct *work)
|
||||
{
|
||||
struct vib_ldo_chip *chip = container_of(work, struct vib_ldo_chip,
|
||||
overdrive_work);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
/* LDO voltage update not required if Vibration disabled */
|
||||
if (!chip->vib_enabled)
|
||||
goto unlock;
|
||||
|
||||
ret = qpnp_vib_ldo_set_voltage(chip, chip->vmax_uV);
|
||||
if (ret < 0) {
|
||||
pr_err("set vibration voltage = %duV failed, ret=%d\n",
|
||||
chip->vmax_uV, ret);
|
||||
qpnp_vib_ldo_enable(chip, false);
|
||||
goto unlock;
|
||||
}
|
||||
pr_debug("voltage set to %d\n", chip->vmax_uV);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&chip->lock);
|
||||
}
|
||||
|
||||
static enum hrtimer_restart vib_overdrive_timer(struct hrtimer *timer)
|
||||
{
|
||||
struct vib_ldo_chip *chip = container_of(timer, struct vib_ldo_chip,
|
||||
overdrive_timer);
|
||||
schedule_work(&chip->overdrive_work);
|
||||
pr_debug("overdrive timer expired\n");
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_show_state(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", chip->vib_enabled);
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_store_state(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
/* At present, nothing to do with setting state */
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_show_duration(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
ktime_t time_rem;
|
||||
s64 time_ms = 0;
|
||||
|
||||
if (hrtimer_active(&chip->stop_timer)) {
|
||||
time_rem = hrtimer_get_remaining(&chip->stop_timer);
|
||||
time_ms = ktime_to_ms(time_rem);
|
||||
}
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%lld\n", time_ms);
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_store_duration(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = kstrtouint(buf, 0, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* setting 0 on duration is NOP for now */
|
||||
if (val <= 0)
|
||||
return count;
|
||||
|
||||
if (val < QPNP_VIB_MIN_PLAY_MS)
|
||||
val = QPNP_VIB_MIN_PLAY_MS;
|
||||
|
||||
if (val > QPNP_VIB_MAX_PLAY_MS)
|
||||
val = QPNP_VIB_MAX_PLAY_MS;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
chip->vib_play_ms = val;
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_show_activate(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
/* For now nothing to show */
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", 0);
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_store_activate(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = kstrtouint(buf, 0, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (val != 0 && val != 1)
|
||||
return count;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
hrtimer_cancel(&chip->stop_timer);
|
||||
chip->state = val;
|
||||
pr_debug("state = %d, time = %llums\n", chip->state, chip->vib_play_ms);
|
||||
mutex_unlock(&chip->lock);
|
||||
schedule_work(&chip->vib_work);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_show_vmax(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", chip->vmax_uV / 1000);
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_store_vmax(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
int data, ret;
|
||||
|
||||
ret = kstrtoint(buf, 10, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data = data * 1000; /* Convert to microvolts */
|
||||
|
||||
/* check against vibrator ldo min/max voltage limits */
|
||||
data = min(data, QPNP_VIB_LDO_VMAX_UV);
|
||||
data = max(data, QPNP_VIB_LDO_VMIN_UV);
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
chip->vmax_uV = data;
|
||||
mutex_unlock(&chip->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct device_attribute qpnp_vib_attrs[] = {
|
||||
__ATTR(state, 0664, qpnp_vib_show_state, qpnp_vib_store_state),
|
||||
__ATTR(duration, 0664, qpnp_vib_show_duration, qpnp_vib_store_duration),
|
||||
__ATTR(activate, 0664, qpnp_vib_show_activate, qpnp_vib_store_activate),
|
||||
__ATTR(vmax_mv, 0664, qpnp_vib_show_vmax, qpnp_vib_store_vmax),
|
||||
};
|
||||
|
||||
static int qpnp_vib_parse_dt(struct device *dev, struct vib_ldo_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(dev->of_node, "qcom,vib-ldo-volt-uv",
|
||||
&chip->vmax_uV);
|
||||
if (ret < 0) {
|
||||
pr_err("qcom,vib-ldo-volt-uv property read failed, ret=%d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
chip->disable_overdrive = of_property_read_bool(dev->of_node,
|
||||
"qcom,disable-overdrive");
|
||||
|
||||
if (of_find_property(dev->of_node, "qcom,vib-overdrive-volt-uv",
|
||||
NULL)) {
|
||||
ret = of_property_read_u32(dev->of_node,
|
||||
"qcom,vib-overdrive-volt-uv",
|
||||
&chip->overdrive_volt_uV);
|
||||
if (ret < 0) {
|
||||
pr_err("qcom,vib-overdrive-volt-uv property read failed, ret=%d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check against vibrator ldo min/max voltage limits */
|
||||
chip->overdrive_volt_uV = min(chip->overdrive_volt_uV,
|
||||
QPNP_VIB_LDO_VMAX_UV);
|
||||
chip->overdrive_volt_uV = max(chip->overdrive_volt_uV,
|
||||
QPNP_VIB_LDO_VMIN_UV);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Dummy functions for brightness */
|
||||
static enum led_brightness qpnp_vib_brightness_get(struct led_classdev *cdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qpnp_vib_brightness_set(struct led_classdev *cdev,
|
||||
enum led_brightness level)
|
||||
{
|
||||
}
|
||||
|
||||
static int qpnp_vibrator_ldo_suspend(struct device *dev)
|
||||
{
|
||||
struct vib_ldo_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
if (!chip->disable_overdrive) {
|
||||
hrtimer_cancel(&chip->overdrive_timer);
|
||||
cancel_work_sync(&chip->overdrive_work);
|
||||
}
|
||||
hrtimer_cancel(&chip->stop_timer);
|
||||
cancel_work_sync(&chip->vib_work);
|
||||
qpnp_vib_ldo_enable(chip, false);
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static SIMPLE_DEV_PM_OPS(qpnp_vibrator_ldo_pm_ops, qpnp_vibrator_ldo_suspend,
|
||||
NULL);
|
||||
|
||||
static int qpnp_vibrator_ldo_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *of_node = pdev->dev.of_node;
|
||||
struct vib_ldo_chip *chip;
|
||||
int i, ret;
|
||||
u32 base;
|
||||
|
||||
ret = of_property_read_u32(of_node, "reg", &base);
|
||||
if (ret < 0) {
|
||||
pr_err("reg property reading failed, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
if (!chip->regmap) {
|
||||
pr_err("couldn't get parent's regmap\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = qpnp_vib_parse_dt(&pdev->dev, chip);
|
||||
if (ret < 0) {
|
||||
pr_err("couldn't parse device tree, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
chip->base = (uint16_t)base;
|
||||
chip->vib_play_ms = QPNP_VIB_PLAY_MS;
|
||||
mutex_init(&chip->lock);
|
||||
INIT_WORK(&chip->vib_work, qpnp_vib_work);
|
||||
INIT_WORK(&chip->overdrive_work, qpnp_vib_overdrive_work);
|
||||
|
||||
hrtimer_init(&chip->stop_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
chip->stop_timer.function = vib_stop_timer;
|
||||
hrtimer_init(&chip->overdrive_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
chip->overdrive_timer.function = vib_overdrive_timer;
|
||||
dev_set_drvdata(&pdev->dev, chip);
|
||||
|
||||
chip->cdev.name = "vibrator";
|
||||
chip->cdev.brightness_get = qpnp_vib_brightness_get;
|
||||
chip->cdev.brightness_set = qpnp_vib_brightness_set;
|
||||
chip->cdev.max_brightness = 100;
|
||||
ret = devm_led_classdev_register(&pdev->dev, &chip->cdev);
|
||||
if (ret < 0) {
|
||||
pr_err("Error in registering led class device, ret=%d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(qpnp_vib_attrs); i++) {
|
||||
ret = sysfs_create_file(&chip->cdev.dev->kobj,
|
||||
&qpnp_vib_attrs[i].attr);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Error in creating sysfs file, ret=%d\n",
|
||||
ret);
|
||||
goto sysfs_fail;
|
||||
}
|
||||
}
|
||||
|
||||
pr_info("Vibrator LDO successfully registered: uV = %d, overdrive = %s\n",
|
||||
chip->vmax_uV,
|
||||
chip->disable_overdrive ? "disabled" : "enabled");
|
||||
return 0;
|
||||
|
||||
sysfs_fail:
|
||||
for (--i; i >= 0; i--)
|
||||
sysfs_remove_file(&chip->cdev.dev->kobj,
|
||||
&qpnp_vib_attrs[i].attr);
|
||||
fail:
|
||||
mutex_destroy(&chip->lock);
|
||||
dev_set_drvdata(&pdev->dev, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qpnp_vibrator_ldo_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct vib_ldo_chip *chip = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
if (!chip->disable_overdrive) {
|
||||
hrtimer_cancel(&chip->overdrive_timer);
|
||||
cancel_work_sync(&chip->overdrive_work);
|
||||
}
|
||||
hrtimer_cancel(&chip->stop_timer);
|
||||
cancel_work_sync(&chip->vib_work);
|
||||
mutex_destroy(&chip->lock);
|
||||
dev_set_drvdata(&pdev->dev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id vibrator_ldo_match_table[] = {
|
||||
{ .compatible = "qcom,qpnp-vibrator-ldo" },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, vibrator_ldo_match_table);
|
||||
|
||||
static struct platform_driver qpnp_vibrator_ldo_driver = {
|
||||
.driver = {
|
||||
.name = "qcom,qpnp-vibrator-ldo",
|
||||
.of_match_table = vibrator_ldo_match_table,
|
||||
.pm = &qpnp_vibrator_ldo_pm_ops,
|
||||
},
|
||||
.probe = qpnp_vibrator_ldo_probe,
|
||||
.remove = qpnp_vibrator_ldo_remove,
|
||||
};
|
||||
module_platform_driver(qpnp_vibrator_ldo_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QPNP Vibrator-LDO driver");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Reference in New Issue
Block a user