// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2019 MediaTek Inc. * */ #include #include #include #include #include #include #include #include #include #include #include #define VIB_DEVICE "regulator_vibrator" #undef pr_fmt #define pr_fmt(fmt) KBUILD_MODNAME " %s(%d) :" fmt, __func__, __LINE__ #define DEFAULT_MIN_LIMIT 15 struct reg_vibr_config { unsigned int min_volt; unsigned int max_volt; struct regulator *reg; }; struct reg_vibr { atomic_t reg_status; atomic_t vibr_state; atomic_t vibr_shutdown; struct workqueue_struct *vibr_queue; struct work_struct vibr_work; struct led_classdev vibr_cdev; struct reg_vibr_config vibr_conf; struct notifier_block oc_handle; }; static int mt_vibra_init_config(struct device *dev, struct reg_vibr_config *vibr_conf) { int ret; vibr_conf->reg = devm_regulator_get(dev, "vib"); if (IS_ERR(vibr_conf->reg)) { ret = PTR_ERR(vibr_conf->reg); pr_notice("Error load dts: get regulator return %d\n", ret); vibr_conf->reg = NULL; return ret; } ret = of_property_read_u32(dev->of_node, "max-volt", &vibr_conf->max_volt); if (ret) { pr_notice("Error load dts: get max-volt failed!\n"); ret = -EINVAL; return ret; } ret = of_property_read_u32(dev->of_node, "min-volt", &vibr_conf->min_volt); if (ret) { pr_notice("Error load dts: get min-volt failed!\n"); ret = -EINVAL; return ret; } if (vibr_conf->min_volt > vibr_conf->max_volt) { pr_notice("Error load dts: get error voltage(min > max)!\n"); ret = -EINVAL; return ret; } pr_info("vibr_conf %u-%u\n", vibr_conf->min_volt, vibr_conf->max_volt); return ret; } static int vibr_power_set(struct reg_vibr *vibr) { int ret; pr_info("set voltage = %u-%u\n", vibr->vibr_conf.min_volt, vibr->vibr_conf.max_volt); ret = regulator_set_voltage(vibr->vibr_conf.reg, vibr->vibr_conf.min_volt, vibr->vibr_conf.max_volt); if (ret < 0) pr_notice("set voltage fail, ret = %d\n", ret); return ret; } static void vibr_enable(struct reg_vibr *vibr) { pr_info("vibr enable\n"); if (!atomic_read(&vibr->reg_status)) { if (regulator_enable(vibr->vibr_conf.reg)) pr_notice("set vibr_reg enable failed!\n"); else atomic_set(&vibr->reg_status, 1); } else { pr_notice("vibr_reg already enabled.\n"); } } static void vibr_disable(struct reg_vibr *vibr) { pr_info("vibr disable\n"); if (atomic_read(&vibr->reg_status)) { if (regulator_disable(vibr->vibr_conf.reg)) pr_notice("set vibr_reg disable failed!\n"); else atomic_set(&vibr->reg_status, 0); } else { pr_notice("vibr_reg already disabled.\n"); } } static void update_vibrator(struct work_struct *work) { struct reg_vibr *vibr = container_of(work, struct reg_vibr, vibr_work); if (!atomic_read(&vibr->vibr_state)) vibr_disable(vibr); else vibr_enable(vibr); } static int regulator_vibrator_set(struct led_classdev *led_cdev, enum led_brightness value) { struct reg_vibr *vibr = container_of(led_cdev, struct reg_vibr, vibr_cdev); if (atomic_read(&vibr->vibr_shutdown) || value == LED_OFF) atomic_set(&vibr->vibr_state, 0); else atomic_set(&vibr->vibr_state, 1); queue_work(vibr->vibr_queue, &vibr->vibr_work); return 0; } static const struct of_device_id vibr_of_ids[] = { { .compatible = "regulator-vibrator", }, {} }; static int regulator_oc_event(struct notifier_block *nb, unsigned long event, void *data) { struct reg_vibr *vibr = container_of(nb, struct reg_vibr, oc_handle); switch (event) { case REGULATOR_EVENT_OVER_CURRENT: case REGULATOR_EVENT_FAIL: pr_info("get regulator oc event: %lu", event); atomic_set(&vibr->vibr_state, 0); queue_work(vibr->vibr_queue, &vibr->vibr_work); break; default: break; } return NOTIFY_OK; } static int vib_probe(struct platform_device *pdev) { int ret; struct reg_vibr *m_vibr; pr_info("probe start +++"); m_vibr = devm_kzalloc(&pdev->dev, sizeof(struct reg_vibr), GFP_KERNEL); if (!m_vibr) { ret = -ENOMEM; goto err; } m_vibr->vibr_queue = create_singlethread_workqueue(VIB_DEVICE); if (!m_vibr->vibr_queue) { ret = -ENOMEM; pr_notice("unable to create workqueue!\n"); goto err; } ret = mt_vibra_init_config(&pdev->dev, &m_vibr->vibr_conf); if (ret) { pr_notice("failed to parse devicetree(%d)!\n", ret); goto err; } INIT_WORK(&m_vibr->vibr_work, update_vibrator); atomic_set(&m_vibr->vibr_shutdown, 0); if (regulator_is_enabled(m_vibr->vibr_conf.reg)) atomic_set(&m_vibr->reg_status, 1); else atomic_set(&m_vibr->reg_status, 0); ret = of_property_read_string(pdev->dev.of_node, "label", &(m_vibr->vibr_cdev.name)); if (ret) { pr_notice("Error load dts: get regulator label return %d\n", ret); goto err; } m_vibr->vibr_cdev.brightness_set_blocking = regulator_vibrator_set; ret = devm_led_classdev_register(&pdev->dev, &m_vibr->vibr_cdev); if (ret < 0) { pr_info("led class register fail\n"); goto err; } /* register oc notification for this regulator */ m_vibr->oc_handle.notifier_call = regulator_oc_event; ret = devm_regulator_register_notifier(m_vibr->vibr_conf.reg, &m_vibr->oc_handle); if (ret) pr_info("regulator notifier request failed\n"); platform_set_drvdata(pdev, m_vibr); ret = vibr_power_set(m_vibr); if (ret < 0) { pr_info("set voltage for regulator fail\n"); goto err; } pr_info("probe success, end ---"); return 0; err: pr_notice("probe failed(%d), end ---!\n", ret); return ret; } static int __maybe_unused vib_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct reg_vibr *vibr = platform_get_drvdata(pdev); if (atomic_read(&vibr->vibr_state)) { atomic_set(&vibr->vibr_state, 0); vibr_disable(vibr); pr_notice("vibr disabled, enter suspend."); } return 0; } static int vib_remove(struct platform_device *pdev) { struct reg_vibr *vibr = platform_get_drvdata(pdev); cancel_work_sync(&vibr->vibr_work); devm_led_classdev_unregister(&pdev->dev, &vibr->vibr_cdev); return 0; } static void vib_shutdown(struct platform_device *pdev) { struct reg_vibr *vibr = platform_get_drvdata(pdev); pr_info("shutdown: enter!\n"); atomic_set(&vibr->vibr_shutdown, 1); if (atomic_read(&vibr->vibr_state)) { atomic_set(&vibr->vibr_state, 0); pr_info("vibrator will disable!\n"); vibr_disable(vibr); } } 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, .pm = VIB_PM_OPS, .of_match_table = vibr_of_ids, }, }; module_platform_driver(vibrator_driver); MODULE_AUTHOR("Mediatek Corporation"); MODULE_DESCRIPTION("Regulator Vibrator Driver (VIB)"); MODULE_LICENSE("GPL");