// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * struct silent_mode_data : silent mode context data * @gpio_number: GPIO Instance for silent mode monitoring * @irq_number: IRQ Number assigned dynamically for silent mode * @pdev: platform device from device/driver call * @pm_nb: power manager notification block * * Silent mode context data holding all the information needed * by the driver for device/driver interactions */ struct silent_mode_info { int sgpio; int sirq; bool active_low; struct platform_device *pdev; struct gpio_desc *gpiod; struct notifier_block pm_nb; struct delayed_work work; }; static irqreturn_t silent_mode_irq(int irq, void *priv) { struct silent_mode_info *info = priv; mod_delayed_work(system_wq, &info->work, msecs_to_jiffies(200)); return IRQ_HANDLED; } static void silent_mode_handler_work_func(struct work_struct *work) { int irq_trig; struct silent_mode_info *info = container_of(work, struct silent_mode_info, work.work); irq_trig = gpiod_get_value(info->gpiod); dev_info(&info->pdev->dev, "Irq_trig %d\n", irq_trig); pm_silentmode_update(irq_trig, NULL, 0); } /** * silent_mode_init_monitor() - Initialize the Silent Mode monitoring function * @info: pointer to silent_mode_info structure * * This function returns 0 if it succeeds. If an error occurs an * negative number is returned. * * This function will: * - Request GPIO * - Set the GPIO direction as input * - Request a dual-edge GPIO interrupt * * Return: * - 0: if Init passes * - ERRNO: if Init fails */ static int silent_mode_init_monitor(struct silent_mode_info *info) { int result = -EINVAL; dev_info(&info->pdev->dev, "GPIO for Silent boot: %d\n", info->sgpio); if (!gpio_is_valid(info->sgpio)) { dev_info(&info->pdev->dev, "Invalid gpio Request %d\n", info->sgpio); return result; } info->gpiod = gpio_to_desc(info->sgpio); info->active_low = gpiod_is_active_low(info->gpiod); info->sirq = gpiod_to_irq(info->gpiod); result = gpiod_set_debounce(info->gpiod, 15000); if (result < 0) { dev_err(&info->pdev->dev, "%s: GPIO debounce ret %d\n", __func__, result); return result; } INIT_DELAYED_WORK(&info->work, silent_mode_handler_work_func); result = devm_request_any_context_irq(&info->pdev->dev, info->sirq, silent_mode_irq, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "silent_boot_GPIO_handler", info); if (result < 0) dev_err(&info->pdev->dev, "IRQ request %d\n", result); return result; } static int silent_mode_enable_monitor(struct silent_mode_info *info) { int result = 0; dev_info(&info->pdev->dev, "silent_mode:%s\n", __func__); if (info != NULL) { result = gpio_get_value(info->sgpio); if (result != 0) enable_irq(info->sirq); else return -EEXIST; } return 0; } static int silent_mode_disable_monitor(struct silent_mode_info *info) { dev_info(&info->pdev->dev, "silent_mode:%s\n", __func__); if (info != NULL) disable_irq(info->sirq); return 0; } static int forced_mode_enforced(void) { if (pm_silentmode_get_mode() < 0) return 1; return 0; } static int silent_mode_probe(struct platform_device *pdev) { int result = 0; unsigned int flags = GPIOF_IN; struct silent_mode_info *info; if (forced_mode_enforced()) { dev_info(&pdev->dev, "forced_mode: No HW State monitoring\n"); return result; } dev_info(&pdev->dev, "MODE_SILENT/MODE_NON_SILENT\n"); info = devm_kzalloc(&pdev->dev, sizeof(struct silent_mode_info), GFP_KERNEL); if (!info) { dev_info(&pdev->dev, "%s: FAILED: cannot alloc info data\n", __func__); result = -ENOMEM; return result; } info->pdev = pdev; platform_set_drvdata(pdev, info); info->sgpio = of_get_named_gpio(pdev->dev.of_node, "qcom,silent-boot-gpio", 0); result = devm_gpio_request_one(&pdev->dev, info->sgpio, flags, "qti-silent-mode"); if (result < 0) { dev_err(&pdev->dev, "Failed to request GPIO %d, error %d\n", info->sgpio, result); result = -EINVAL; } result = silent_mode_init_monitor(info); if (result < 0) { dev_err(&pdev->dev, "Failed Init: %d\n", result); result = -EINVAL; } return result; } #ifdef CONFIG_PM static int silent_mode_suspend_late(struct device *dev) { int ret = 0; struct platform_device *pdev = to_platform_device(dev); struct silent_mode_info *info = platform_get_drvdata(pdev); ret = silent_mode_disable_monitor(info); dev_info(&info->pdev->dev, "silent_mode:%s\n", __func__); return ret; } static int silent_mode_resume_early(struct device *dev) { int ret = 0; struct platform_device *pdev = to_platform_device(dev); struct silent_mode_info *info = platform_get_drvdata(pdev); ret = silent_mode_enable_monitor(info); dev_info(&info->pdev->dev, "silent_mode:%s\n", __func__); return ret; } static const struct dev_pm_ops silent_mode_pm_ops = { .suspend_late = silent_mode_suspend_late, .resume_early = silent_mode_resume_early, }; #endif static int silent_mode_remove(struct platform_device *pdev) { dev_dbg(&pdev->dev, "silent_mode: Entered %s\n", __func__); return 0; } static const struct of_device_id silent_mode_match_table[] = { {.compatible = "qcom,silent-mode" }, { } }; static struct platform_driver silent_mode_driver = { .probe = silent_mode_probe, .remove = silent_mode_remove, .driver = { .name = "qcom-silent-monitoring-mode", .of_match_table = silent_mode_match_table, #ifdef CONFIG_PM .pm = &silent_mode_pm_ops, #endif }, }; module_platform_driver(silent_mode_driver); MODULE_DESCRIPTION("Silent Mode Monitoring Driver"); MODULE_LICENSE("GPL v2");