|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * DesignWare PWM Controller driver (PCI part) | 
|  | * | 
|  | * Copyright (C) 2018-2020 Intel Corporation | 
|  | * | 
|  | * Author: Felipe Balbi (Intel) | 
|  | * Author: Jarkko Nikula <jarkko.nikula@linux.intel.com> | 
|  | * Author: Raymond Tan <raymond.tan@intel.com> | 
|  | * | 
|  | * Limitations: | 
|  | * - The hardware cannot generate a 0 % or 100 % duty cycle. Both high and low | 
|  | *   periods are one or more input clock periods long. | 
|  | */ | 
|  |  | 
|  | #define DEFAULT_MOUDLE_NAMESPACE dwc_pwm | 
|  |  | 
|  | #include <linux/bitops.h> | 
|  | #include <linux/export.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/pci.h> | 
|  | #include <linux/pm_runtime.h> | 
|  | #include <linux/pwm.h> | 
|  |  | 
|  | #include "pwm-dwc.h" | 
|  |  | 
|  | /* Elkhart Lake */ | 
|  | static const struct dwc_pwm_info ehl_pwm_info = { | 
|  | .nr = 2, | 
|  | .size = 0x1000, | 
|  | }; | 
|  |  | 
|  | static int dwc_pwm_init_one(struct device *dev, struct dwc_pwm_drvdata *ddata, unsigned int idx) | 
|  | { | 
|  | struct pwm_chip *chip; | 
|  | struct dwc_pwm *dwc; | 
|  | int ret; | 
|  |  | 
|  | chip = dwc_pwm_alloc(dev); | 
|  | if (IS_ERR(chip)) | 
|  | return PTR_ERR(chip); | 
|  |  | 
|  | dwc = to_dwc_pwm(chip); | 
|  | dwc->base = ddata->io_base + (ddata->info->size * idx); | 
|  |  | 
|  | ret = devm_pwmchip_add(dev, chip); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ddata->chips[idx] = chip; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int dwc_pwm_probe(struct pci_dev *pci, const struct pci_device_id *id) | 
|  | { | 
|  | const struct dwc_pwm_info *info; | 
|  | struct device *dev = &pci->dev; | 
|  | struct dwc_pwm_drvdata *ddata; | 
|  | unsigned int idx; | 
|  | int ret; | 
|  |  | 
|  | ret = pcim_enable_device(pci); | 
|  | if (ret) | 
|  | return dev_err_probe(dev, ret, "Failed to enable device\n"); | 
|  |  | 
|  | pci_set_master(pci); | 
|  |  | 
|  | ret = pcim_iomap_regions(pci, BIT(0), pci_name(pci)); | 
|  | if (ret) | 
|  | return dev_err_probe(dev, ret, "Failed to iomap PCI BAR\n"); | 
|  |  | 
|  | info = (const struct dwc_pwm_info *)id->driver_data; | 
|  | ddata = devm_kzalloc(dev, struct_size(ddata, chips, info->nr), GFP_KERNEL); | 
|  | if (!ddata) | 
|  | return -ENOMEM; | 
|  |  | 
|  | /* | 
|  | * No need to check for pcim_iomap_table() failure, | 
|  | * pcim_iomap_regions() already does it for us. | 
|  | */ | 
|  | ddata->io_base = pcim_iomap_table(pci)[0]; | 
|  | ddata->info = info; | 
|  |  | 
|  | for (idx = 0; idx < ddata->info->nr; idx++) { | 
|  | ret = dwc_pwm_init_one(dev, ddata, idx); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | dev_set_drvdata(dev, ddata); | 
|  |  | 
|  | pm_runtime_put(dev); | 
|  | pm_runtime_allow(dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void dwc_pwm_remove(struct pci_dev *pci) | 
|  | { | 
|  | pm_runtime_forbid(&pci->dev); | 
|  | pm_runtime_get_noresume(&pci->dev); | 
|  | } | 
|  |  | 
|  | static int dwc_pwm_suspend(struct device *dev) | 
|  | { | 
|  | struct dwc_pwm_drvdata *ddata = dev_get_drvdata(dev); | 
|  | unsigned int idx; | 
|  |  | 
|  | for (idx = 0; idx < ddata->info->nr; idx++) { | 
|  | struct pwm_chip *chip = ddata->chips[idx]; | 
|  | struct dwc_pwm *dwc = to_dwc_pwm(chip); | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; i < DWC_TIMERS_TOTAL; i++) { | 
|  | if (chip->pwms[i].state.enabled) { | 
|  | dev_err(dev, "PWM %u in use by consumer (%s)\n", | 
|  | i, chip->pwms[i].label); | 
|  | return -EBUSY; | 
|  | } | 
|  | dwc->ctx[i].cnt = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(i)); | 
|  | dwc->ctx[i].cnt2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(i)); | 
|  | dwc->ctx[i].ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(i)); | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int dwc_pwm_resume(struct device *dev) | 
|  | { | 
|  | struct dwc_pwm_drvdata *ddata = dev_get_drvdata(dev); | 
|  | unsigned int idx; | 
|  |  | 
|  | for (idx = 0; idx < ddata->info->nr; idx++) { | 
|  | struct pwm_chip *chip = ddata->chips[idx]; | 
|  | struct dwc_pwm *dwc = to_dwc_pwm(chip); | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; i < DWC_TIMERS_TOTAL; i++) { | 
|  | dwc_pwm_writel(dwc, dwc->ctx[i].cnt, DWC_TIM_LD_CNT(i)); | 
|  | dwc_pwm_writel(dwc, dwc->ctx[i].cnt2, DWC_TIM_LD_CNT2(i)); | 
|  | dwc_pwm_writel(dwc, dwc->ctx[i].ctrl, DWC_TIM_CTRL(i)); | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static DEFINE_SIMPLE_DEV_PM_OPS(dwc_pwm_pm_ops, dwc_pwm_suspend, dwc_pwm_resume); | 
|  |  | 
|  | static const struct pci_device_id dwc_pwm_id_table[] = { | 
|  | { PCI_VDEVICE(INTEL, 0x4bb7), (kernel_ulong_t)&ehl_pwm_info }, | 
|  | {  }	/* Terminating Entry */ | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(pci, dwc_pwm_id_table); | 
|  |  | 
|  | static struct pci_driver dwc_pwm_driver = { | 
|  | .name = "pwm-dwc", | 
|  | .probe = dwc_pwm_probe, | 
|  | .remove = dwc_pwm_remove, | 
|  | .id_table = dwc_pwm_id_table, | 
|  | .driver = { | 
|  | .pm = pm_sleep_ptr(&dwc_pwm_pm_ops), | 
|  | }, | 
|  | }; | 
|  |  | 
|  | module_pci_driver(dwc_pwm_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Felipe Balbi (Intel)"); | 
|  | MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>"); | 
|  | MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>"); | 
|  | MODULE_DESCRIPTION("DesignWare PWM Controller"); | 
|  | MODULE_LICENSE("GPL"); |