// SPDX-License-Identifier: GPL-2.0-only // SPDX-FileCopyrightText: Copyright (c) 2016-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. #include #include #include #include #include #include #include #include "isc-pwm-priv.h" /* * Below values are configured during suspend. * Invalid values are chosen so that PWM * configuration in resume works fine. * Period is chosen as least non-zero value * and duty-ratio zero. */ #define PWM_SUSPEND_PERIOD 1 #define PWM_SUSPEND_DUTY_RATIO 0 static const struct of_device_id isc_pwm_of_match[] = { { .compatible = "nvidia, isc-pwm", .data = NULL }, {}, }; static inline struct isc_pwm_info *to_isc_pwm_info(struct pwm_chip *chip) { #if defined(NV_PWM_CHIP_STRUCT_HAS_STRUCT_DEVICE) return pwmchip_get_drvdata(chip); #else return container_of(chip, struct isc_pwm_info, chip); #endif } #if defined(NV_PWM_OPS_STRUCT_HAS_CONFIG) /* Linux 6.0 */ static int isc_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct isc_pwm_info *info = to_isc_pwm_info(chip); int err = 0; if (!chip || !pwm) return -EINVAL; if (info->force_on) return err; mutex_lock(&info->mutex); if (atomic_inc_return(&info->in_use) == 1) err = pwm_enable(info->pwm); mutex_unlock(&info->mutex); return err; } static void isc_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct isc_pwm_info *info = to_isc_pwm_info(chip); int atomic_val; mutex_lock(&info->mutex); atomic_val = atomic_read(&info->in_use); if (atomic_val > 0) { if (atomic_dec_and_test(&info->in_use)) pwm_disable(info->pwm); } mutex_unlock(&info->mutex); } static int isc_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct isc_pwm_info *info = to_isc_pwm_info(chip); int err = 0; if (info->force_on) return err; mutex_lock(&info->mutex); err = pwm_config(info->pwm, duty_ns, period_ns); mutex_unlock(&info->mutex); return err; } #endif static struct pwm_device *of_isc_pwm_xlate(struct pwm_chip *pc, const struct of_phandle_args *args) { struct pwm_device *pwm; struct isc_pwm_info *info = to_isc_pwm_info(pc); #if defined(NV_PWM_CHIP_STRUCT_HAS_STRUCT_DEVICE) struct device *dev = &pc->dev; #else struct device *dev = pc->dev; #endif int err = 0; pwm = of_pwm_xlate_with_flags(pc, args); if (IS_ERR(pwm)) return NULL; if (!pwm->args.period) { dev_err(dev, "Period should be larger than 0\n"); return NULL; } if (info->force_on) { err = pwm_config(info->pwm, args->args[1]/4, args->args[1]); if (err) { dev_err(dev, "can't config PWM: %d\n", err); return NULL; } err = pwm_enable(info->pwm); if (err) { dev_err(dev, "can't enable PWM: %d\n", err); return NULL; } } else { err = pwm_config(pwm, args->args[1]/4, args->args[1]); if (err) { dev_err(dev, "can't config PWM: %d\n", err); return NULL; } } return pwm; } static const struct pwm_ops isc_pwm_ops = { #if defined(NV_PWM_OPS_STRUCT_HAS_CONFIG) /* Linux 6.0 */ .config = isc_pwm_config, .enable = isc_pwm_enable, .disable = isc_pwm_disable, #endif #if defined(NV_PWM_OPS_STRUCT_HAS_OWNER) /* Linux 6.7 */ .owner = THIS_MODULE, #endif }; static int isc_pwm_probe(struct platform_device *pdev) { struct isc_pwm_info *info = NULL; struct pwm_chip *chip; int err = 0, npwm; bool force_on = false; dev_info(&pdev->dev, "%sing...\n", __func__); err = of_property_read_u32(pdev->dev.of_node, "npwm", &npwm); if (err < 0) { dev_err(&pdev->dev, "npwm is not defined: %d\n", err); return err; } #if defined(NV_PWM_CHIP_STRUCT_HAS_STRUCT_DEVICE) chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*info)); if (IS_ERR(chip)) return PTR_ERR(chip); info = to_isc_pwm_info(chip); #else info = devm_kzalloc( &pdev->dev, sizeof(struct isc_pwm_info), GFP_KERNEL); if (!info) return -ENOMEM; chip = &info->chip; chip->dev = &pdev->dev; chip->npwm = npwm; #endif atomic_set(&info->in_use, 0); mutex_init(&info->mutex); force_on = of_property_read_bool(pdev->dev.of_node, "force_on"); chip->ops = &isc_pwm_ops; #if defined(NV_PWM_CHIP_STRUCT_HAS_BASE_ARG) chip->base = -1; #endif chip->of_xlate = of_isc_pwm_xlate; info->force_on = force_on; err = pwmchip_add(chip); if (err < 0) { dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", err); return err; } platform_set_drvdata(pdev, chip); info->pwm = devm_pwm_get(&pdev->dev, NULL); if (!IS_ERR(info->pwm)) { pwm_disable(info->pwm); dev_info(&pdev->dev, "%s success to get PWM\n", __func__); } else { pwmchip_remove(chip); err = PTR_ERR(info->pwm); if (err != -EPROBE_DEFER) dev_err(&pdev->dev, "%s: fail to get PWM\n", __func__); } return err; } static int isc_pwm_remove(struct platform_device *pdev) { struct pwm_chip *chip = platform_get_drvdata(pdev); pwmchip_remove(chip); return 0; } static int isc_pwm_suspend(struct device *dev) { struct pwm_chip *chip = dev_get_drvdata(dev); struct isc_pwm_info *info = to_isc_pwm_info(chip); if (info == NULL) { dev_err(dev, "%s: fail to get info\n", __func__); } else { if (!IS_ERR(info->pwm)) { pwm_disable(info->pwm); pwm_config(info->pwm, PWM_SUSPEND_DUTY_RATIO, PWM_SUSPEND_PERIOD); } } return 0; } static int isc_pwm_resume(struct device *dev) { /* Do nothing */ return 0; } static const struct dev_pm_ops isc_pwm_pm_ops = { .suspend = isc_pwm_suspend, .resume = isc_pwm_resume, .runtime_suspend = isc_pwm_suspend, .runtime_resume = isc_pwm_resume, }; #if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */ static void isc_pwm_remove_wrapper(struct platform_device *pdev) { isc_pwm_remove(pdev); } #else static int isc_pwm_remove_wrapper(struct platform_device *pdev) { return isc_pwm_remove(pdev); } #endif static struct platform_driver isc_pwm_driver = { .driver = { .name = "isc-pwm", .owner = THIS_MODULE, .of_match_table = isc_pwm_of_match, .pm = &isc_pwm_pm_ops, }, .probe = isc_pwm_probe, .remove = isc_pwm_remove_wrapper, }; module_platform_driver(isc_pwm_driver); MODULE_AUTHOR("Junghyun Kim "); MODULE_DESCRIPTION("ISC PWM driver"); MODULE_LICENSE("GPL v2"); MODULE_DEVICE_TABLE(of, isc_pwm_of_match); MODULE_SOFTDEP("pre: isc_dev");