384 lines
8.8 KiB
C
384 lines
8.8 KiB
C
|
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||
|
|
/*
|
||
|
|
* Copyright (c) 2019 MediaTek Inc.
|
||
|
|
*/
|
||
|
|
#include <linux/init.h>
|
||
|
|
#include <linux/module.h>
|
||
|
|
#include <linux/kernel.h>
|
||
|
|
#include <linux/types.h>
|
||
|
|
#include <linux/device.h>
|
||
|
|
#include <linux/workqueue.h>
|
||
|
|
#include <linux/leds.h>
|
||
|
|
#include <linux/hrtimer.h>
|
||
|
|
#include <linux/err.h>
|
||
|
|
#include <linux/platform_device.h>
|
||
|
|
#include <linux/spinlock.h>
|
||
|
|
#include <linux/jiffies.h>
|
||
|
|
#include <linux/timer.h>
|
||
|
|
#include <linux/mod_devicetable.h>
|
||
|
|
#include <vibrator.h>
|
||
|
|
#include <vibrator_hal.h>
|
||
|
|
#include <mt-plat/upmu_common.h>
|
||
|
|
#ifndef CONFIG_MACH_MT6771
|
||
|
|
#include <linux/regulator/consumer.h>
|
||
|
|
#include <linux/of.h>
|
||
|
|
#endif
|
||
|
|
#define VIB_DEVICE "mtk_vibrator"
|
||
|
|
#define VIB_TAG "[vibrator]"
|
||
|
|
#undef pr_fmt
|
||
|
|
#define pr_fmt(fmt) KBUILD_MODNAME " %s(%d) :" fmt, __func__, __LINE__
|
||
|
|
|
||
|
|
struct mt_vibr {
|
||
|
|
struct workqueue_struct *vibr_queue;
|
||
|
|
struct work_struct vibr_onwork;
|
||
|
|
struct work_struct vibr_offwork;
|
||
|
|
struct hrtimer vibr_timer;
|
||
|
|
int ldo_state;
|
||
|
|
int shutdown_flag;
|
||
|
|
#ifndef CONFIG_MACH_MT6771
|
||
|
|
struct regulator *reg;
|
||
|
|
#endif
|
||
|
|
atomic_t vibr_dur;
|
||
|
|
spinlock_t vibr_lock;
|
||
|
|
atomic_t vibr_state;
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct mt_vibr *g_mt_vib;
|
||
|
|
static int vibr_Enable(void)
|
||
|
|
{
|
||
|
|
if (!g_mt_vib->ldo_state) {
|
||
|
|
pr_info("vibr enable");
|
||
|
|
#ifndef CONFIG_MACH_MT6771
|
||
|
|
vibr_Enable_HW(g_mt_vib->reg);
|
||
|
|
#else
|
||
|
|
vibr_Enable_HW();
|
||
|
|
#endif
|
||
|
|
g_mt_vib->ldo_state = 1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int vibr_Disable(void)
|
||
|
|
{
|
||
|
|
if (g_mt_vib->ldo_state) {
|
||
|
|
pr_info("vibr disable");
|
||
|
|
#ifndef CONFIG_MACH_MT6771
|
||
|
|
vibr_Disable_HW(g_mt_vib->reg);
|
||
|
|
#else
|
||
|
|
vibr_Disable_HW();
|
||
|
|
#endif
|
||
|
|
g_mt_vib->ldo_state = 0;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void on_vibrator(struct work_struct *work)
|
||
|
|
{
|
||
|
|
pr_info("update vibrator enable, ldo=%d", g_mt_vib->ldo_state);
|
||
|
|
vibr_Enable();
|
||
|
|
}
|
||
|
|
|
||
|
|
static void off_vibrator(struct work_struct *work)
|
||
|
|
{
|
||
|
|
pr_info("update vibrator disable, ldo=%d", g_mt_vib->ldo_state);
|
||
|
|
vibr_Disable();
|
||
|
|
}
|
||
|
|
|
||
|
|
static void vibrator_enable(unsigned int dur, unsigned int activate)
|
||
|
|
{
|
||
|
|
unsigned long flags;
|
||
|
|
struct vibrator_hw *hw = mt_get_cust_vibrator_hw();
|
||
|
|
|
||
|
|
hrtimer_cancel(&g_mt_vib->vibr_timer);
|
||
|
|
pr_info(VIB_TAG "cancel hrtimer, cust:%dms, value:%u, activate:%d, shutdown:%d\n",
|
||
|
|
hw->vib_timer, dur, activate, g_mt_vib->shutdown_flag);
|
||
|
|
cancel_work_sync(&g_mt_vib->vibr_onwork);
|
||
|
|
spin_lock_irqsave(&g_mt_vib->vibr_lock, flags);
|
||
|
|
|
||
|
|
if (activate == 0 || g_mt_vib->shutdown_flag == 1) {
|
||
|
|
atomic_set(&g_mt_vib->vibr_state, 0);
|
||
|
|
queue_work(g_mt_vib->vibr_queue, &g_mt_vib->vibr_offwork);
|
||
|
|
} else {
|
||
|
|
#ifdef CUST_VIBR_LIMIT
|
||
|
|
if (dur > hw->vib_limit && dur < hw->vib_timer)
|
||
|
|
#else
|
||
|
|
if (dur >= 10 && dur < hw->vib_timer)
|
||
|
|
#endif
|
||
|
|
dur = hw->vib_timer;
|
||
|
|
|
||
|
|
dur = (dur > 15000 ? 15000 : dur);
|
||
|
|
atomic_set(&g_mt_vib->vibr_state, 1);
|
||
|
|
queue_work(g_mt_vib->vibr_queue, &g_mt_vib->vibr_onwork);
|
||
|
|
hrtimer_start(&g_mt_vib->vibr_timer,
|
||
|
|
ktime_set(dur / 1000, (dur % 1000) * 1000000),
|
||
|
|
HRTIMER_MODE_REL);
|
||
|
|
}
|
||
|
|
spin_unlock_irqrestore(&g_mt_vib->vibr_lock, flags);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void vibrator_oc_handler(void)
|
||
|
|
{
|
||
|
|
pr_info(VIB_TAG "%s: disable vibr for oc intr happened\n", __func__);
|
||
|
|
vibrator_enable(0, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer)
|
||
|
|
{
|
||
|
|
struct mt_vibr *vibr = container_of(timer, struct mt_vibr, vibr_timer);
|
||
|
|
|
||
|
|
atomic_set(&vibr->vibr_state, 0);
|
||
|
|
|
||
|
|
pr_info(VIB_TAG "set vibr_state 0");
|
||
|
|
queue_work(vibr->vibr_queue, &vibr->vibr_offwork);
|
||
|
|
return HRTIMER_NORESTART;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct of_device_id vibr_of_ids[] = {
|
||
|
|
{ .compatible = "mediatek,vibrator", },
|
||
|
|
{}
|
||
|
|
};
|
||
|
|
|
||
|
|
static atomic_t vib_state;
|
||
|
|
|
||
|
|
static ssize_t vibr_activate_show(struct device *dev,
|
||
|
|
struct device_attribute *attr, char *buf)
|
||
|
|
{
|
||
|
|
return sprintf(buf, "%d\n", atomic_read(&g_mt_vib->vibr_state));
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t vibr_activate_store(struct device *dev,
|
||
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
||
|
|
{
|
||
|
|
unsigned int activate = 0, dur = 0;
|
||
|
|
ssize_t ret;
|
||
|
|
|
||
|
|
ret = kstrtouint(buf, 10, &activate);
|
||
|
|
if (ret) {
|
||
|
|
pr_err(VIB_TAG "set activate fail\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
dur = atomic_read(&g_mt_vib->vibr_dur);
|
||
|
|
vibrator_enable(dur, activate);
|
||
|
|
ret = size;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t vibr_state_show(struct device *dev,
|
||
|
|
struct device_attribute *attr, char *buf)
|
||
|
|
{
|
||
|
|
return sprintf(buf, "%d\n", atomic_read(&vib_state));
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t vibr_state_store(struct device *dev,
|
||
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
||
|
|
{
|
||
|
|
unsigned int state = 0;
|
||
|
|
ssize_t ret;
|
||
|
|
|
||
|
|
ret = kstrtouint(buf, 10, &state);
|
||
|
|
if (ret) {
|
||
|
|
pr_err(VIB_TAG "set state fail\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
atomic_set(&vib_state, state);
|
||
|
|
|
||
|
|
ret = size;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
static ssize_t vibr_duration_store(struct device *dev,
|
||
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
||
|
|
{
|
||
|
|
unsigned int duration = 0;
|
||
|
|
ssize_t ret;
|
||
|
|
|
||
|
|
ret = kstrtouint(buf, 10, &duration);
|
||
|
|
if (ret) {
|
||
|
|
pr_err(VIB_TAG "set duration fail\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
atomic_set(&g_mt_vib->vibr_dur, duration);
|
||
|
|
ret = size;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static DEVICE_ATTR(activate, 0644, vibr_activate_show, vibr_activate_store);
|
||
|
|
static DEVICE_ATTR(state, 0644, vibr_state_show, vibr_state_store);
|
||
|
|
static DEVICE_ATTR(duration, 0644, NULL, vibr_duration_store);
|
||
|
|
|
||
|
|
static struct attribute *activate_attrs[] = {
|
||
|
|
&dev_attr_activate.attr,
|
||
|
|
NULL,
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct attribute *state_attrs[] = {
|
||
|
|
&dev_attr_state.attr,
|
||
|
|
NULL,
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct attribute *duration_attrs[] = {
|
||
|
|
&dev_attr_duration.attr,
|
||
|
|
NULL,
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct attribute_group activate_group = {
|
||
|
|
.attrs = activate_attrs,
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct attribute_group state_group = {
|
||
|
|
.attrs = state_attrs,
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct attribute_group duration_group = {
|
||
|
|
.attrs = duration_attrs,
|
||
|
|
};
|
||
|
|
|
||
|
|
static const struct attribute_group *vibr_group[] = {
|
||
|
|
&activate_group,
|
||
|
|
&state_group,
|
||
|
|
&duration_group,
|
||
|
|
NULL
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct led_classdev led_vibr = {
|
||
|
|
.name = "vibrator",
|
||
|
|
.groups = vibr_group,
|
||
|
|
};
|
||
|
|
|
||
|
|
static int vib_probe(struct platform_device *pdev)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
struct mt_vibr *vibr;
|
||
|
|
|
||
|
|
init_vibr_oc_handler(vibrator_oc_handler);
|
||
|
|
|
||
|
|
vibr = devm_kzalloc(&pdev->dev, sizeof(*vibr), GFP_KERNEL);
|
||
|
|
if (!vibr)
|
||
|
|
return -ENOMEM;
|
||
|
|
|
||
|
|
ret = devm_led_classdev_register(&pdev->dev, &led_vibr);
|
||
|
|
if (ret < 0) {
|
||
|
|
pr_err(VIB_TAG "led class register fail\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
vibr->vibr_queue = create_singlethread_workqueue(VIB_DEVICE);
|
||
|
|
if (!vibr->vibr_queue) {
|
||
|
|
pr_err(VIB_TAG "unable to create workqueue\n");
|
||
|
|
return -ENODATA;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifndef CONFIG_MACH_MT6771
|
||
|
|
vibr->reg = devm_regulator_get(&pdev->dev, "vibr");
|
||
|
|
if (IS_ERR(vibr->reg)) {
|
||
|
|
ret = PTR_ERR(vibr->reg);
|
||
|
|
pr_info("Error load dts: get regulator return %d\n", ret);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
INIT_WORK(&vibr->vibr_onwork, on_vibrator);
|
||
|
|
INIT_WORK(&vibr->vibr_offwork, off_vibrator);
|
||
|
|
spin_lock_init(&vibr->vibr_lock);
|
||
|
|
vibr->shutdown_flag = 0;
|
||
|
|
atomic_set(&vibr->vibr_state, 0);
|
||
|
|
hrtimer_init(&vibr->vibr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||
|
|
vibr->vibr_timer.function = vibrator_timer_func;
|
||
|
|
|
||
|
|
dev_set_drvdata(&pdev->dev, vibr);
|
||
|
|
g_mt_vib = vibr;
|
||
|
|
init_cust_vibrator_dtsi(pdev);
|
||
|
|
vibr_power_set();
|
||
|
|
|
||
|
|
pr_debug(VIB_TAG "probe done\n");
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int vib_remove(struct platform_device *pdev)
|
||
|
|
{
|
||
|
|
struct mt_vibr *vibr = dev_get_drvdata(&pdev->dev);
|
||
|
|
|
||
|
|
cancel_work_sync(&vibr->vibr_onwork);
|
||
|
|
cancel_work_sync(&vibr->vibr_offwork);
|
||
|
|
hrtimer_cancel(&vibr->vibr_timer);
|
||
|
|
devm_led_classdev_unregister(&pdev->dev, &led_vibr);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void vib_shutdown(struct platform_device *pdev)
|
||
|
|
{
|
||
|
|
unsigned long flags;
|
||
|
|
struct mt_vibr *vibr = dev_get_drvdata(&pdev->dev);
|
||
|
|
|
||
|
|
pr_debug(VIB_TAG "shutdown: enter!\n");
|
||
|
|
spin_lock_irqsave(&vibr->vibr_lock, flags);
|
||
|
|
vibr->shutdown_flag = 1;
|
||
|
|
if (atomic_read(&vibr->vibr_state)) {
|
||
|
|
atomic_set(&vibr->vibr_state, 0);
|
||
|
|
spin_unlock_irqrestore(&vibr->vibr_lock, flags);
|
||
|
|
pr_debug(VIB_TAG "%s: vibrator will disable\n", __func__);
|
||
|
|
vibr_Disable();
|
||
|
|
} else {
|
||
|
|
spin_unlock_irqrestore(&vibr->vibr_lock, flags);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int vib_suspend(struct device *dev)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
struct platform_device *pdev = to_platform_device(dev);
|
||
|
|
struct mt_vibr *vibr = platform_get_drvdata(pdev);
|
||
|
|
|
||
|
|
if (atomic_read(&vibr->vibr_state)) {
|
||
|
|
atomic_set(&vibr->vibr_state, 0);
|
||
|
|
ret = vibr_Disable();
|
||
|
|
pr_info("vibr disbale vibr ret=%d, enter suspend.", ret);
|
||
|
|
}
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static SIMPLE_DEV_PM_OPS(vib_pm_ops, vib_suspend, NULL);
|
||
|
|
|
||
|
|
#define VIB_PM_OPS (&vib_pm_ops)
|
||
|
|
|
||
|
|
static struct platform_driver vibrator_driver = {
|
||
|
|
.probe = vib_probe,
|
||
|
|
.remove = vib_remove,
|
||
|
|
.shutdown = vib_shutdown,
|
||
|
|
.driver = {
|
||
|
|
.name = VIB_DEVICE,
|
||
|
|
.owner = THIS_MODULE,
|
||
|
|
.pm = VIB_PM_OPS,
|
||
|
|
#ifdef CONFIG_OF
|
||
|
|
.of_match_table = vibr_of_ids,
|
||
|
|
#endif
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
static int vib_mod_init(void)
|
||
|
|
{
|
||
|
|
s32 ret;
|
||
|
|
|
||
|
|
ret = platform_driver_register(&vibrator_driver);
|
||
|
|
if (ret) {
|
||
|
|
pr_err(VIB_TAG "Unable to register driver (%d)\n", ret);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
pr_debug(VIB_TAG "init Done\n");
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void vib_mod_exit(void)
|
||
|
|
{
|
||
|
|
pr_debug(VIB_TAG "%s: Done\n", __func__);
|
||
|
|
}
|
||
|
|
|
||
|
|
module_init(vib_mod_init);
|
||
|
|
module_exit(vib_mod_exit);
|
||
|
|
MODULE_AUTHOR("MediaTek Inc.");
|
||
|
|
MODULE_DESCRIPTION("MTK Vibrator Driver (VIB)");
|
||
|
|
MODULE_LICENSE("GPL");
|