Commit | Line | Data |
---|---|---|
1ed2b3fc JN |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
721ee188 | 3 | * DesignWare PWM Controller driver (PCI part) |
1ed2b3fc JN |
4 | * |
5 | * Copyright (C) 2018-2020 Intel Corporation | |
6 | * | |
7 | * Author: Felipe Balbi (Intel) | |
8 | * Author: Jarkko Nikula <jarkko.nikula@linux.intel.com> | |
9 | * Author: Raymond Tan <raymond.tan@intel.com> | |
10 | * | |
11 | * Limitations: | |
12 | * - The hardware cannot generate a 0 % or 100 % duty cycle. Both high and low | |
13 | * periods are one or more input clock periods long. | |
14 | */ | |
15 | ||
721ee188 BD |
16 | #define DEFAULT_MOUDLE_NAMESPACE dwc_pwm |
17 | ||
1ed2b3fc JN |
18 | #include <linux/bitops.h> |
19 | #include <linux/export.h> | |
20 | #include <linux/kernel.h> | |
21 | #include <linux/module.h> | |
22 | #include <linux/pci.h> | |
23 | #include <linux/pm_runtime.h> | |
24 | #include <linux/pwm.h> | |
25 | ||
721ee188 | 26 | #include "pwm-dwc.h" |
a357d149 | 27 | |
ebf2c89e RJ |
28 | /* Elkhart Lake */ |
29 | static const struct dwc_pwm_info ehl_pwm_info = { | |
30 | .nr = 2, | |
31 | .size = 0x1000, | |
32 | }; | |
33 | ||
a2ac1cbc | 34 | static int dwc_pwm_init_one(struct device *dev, struct dwc_pwm_drvdata *ddata, unsigned int idx) |
1ed2b3fc | 35 | { |
aaa3cc29 | 36 | struct pwm_chip *chip; |
1ed2b3fc | 37 | struct dwc_pwm *dwc; |
a2ac1cbc | 38 | int ret; |
1ed2b3fc | 39 | |
aaa3cc29 UKK |
40 | chip = dwc_pwm_alloc(dev); |
41 | if (IS_ERR(chip)) | |
42 | return PTR_ERR(chip); | |
ebf2c89e | 43 | |
aaa3cc29 | 44 | dwc = to_dwc_pwm(chip); |
a2ac1cbc | 45 | dwc->base = ddata->io_base + (ddata->info->size * idx); |
ebf2c89e | 46 | |
a2ac1cbc RJ |
47 | ret = devm_pwmchip_add(dev, chip); |
48 | if (ret) | |
49 | return ret; | |
50 | ||
51 | ddata->chips[idx] = chip; | |
52 | return 0; | |
ebf2c89e RJ |
53 | } |
54 | ||
55 | static int dwc_pwm_probe(struct pci_dev *pci, const struct pci_device_id *id) | |
56 | { | |
57 | const struct dwc_pwm_info *info; | |
58 | struct device *dev = &pci->dev; | |
a2ac1cbc RJ |
59 | struct dwc_pwm_drvdata *ddata; |
60 | unsigned int idx; | |
61 | int ret; | |
1ed2b3fc JN |
62 | |
63 | ret = pcim_enable_device(pci); | |
9e3440d2 RJ |
64 | if (ret) |
65 | return dev_err_probe(dev, ret, "Failed to enable device\n"); | |
1ed2b3fc JN |
66 | |
67 | pci_set_master(pci); | |
68 | ||
69 | ret = pcim_iomap_regions(pci, BIT(0), pci_name(pci)); | |
9e3440d2 RJ |
70 | if (ret) |
71 | return dev_err_probe(dev, ret, "Failed to iomap PCI BAR\n"); | |
1ed2b3fc | 72 | |
ebf2c89e | 73 | info = (const struct dwc_pwm_info *)id->driver_data; |
a2ac1cbc RJ |
74 | ddata = devm_kzalloc(dev, struct_size(ddata, chips, info->nr), GFP_KERNEL); |
75 | if (!ddata) | |
76 | return -ENOMEM; | |
77 | ||
78 | /* | |
79 | * No need to check for pcim_iomap_table() failure, | |
80 | * pcim_iomap_regions() already does it for us. | |
81 | */ | |
82 | ddata->io_base = pcim_iomap_table(pci)[0]; | |
83 | ddata->info = info; | |
84 | ||
85 | for (idx = 0; idx < ddata->info->nr; idx++) { | |
86 | ret = dwc_pwm_init_one(dev, ddata, idx); | |
ebf2c89e RJ |
87 | if (ret) |
88 | return ret; | |
89 | } | |
1ed2b3fc | 90 | |
a2ac1cbc RJ |
91 | dev_set_drvdata(dev, ddata); |
92 | ||
1ed2b3fc JN |
93 | pm_runtime_put(dev); |
94 | pm_runtime_allow(dev); | |
95 | ||
96 | return 0; | |
97 | } | |
98 | ||
99 | static void dwc_pwm_remove(struct pci_dev *pci) | |
100 | { | |
1ed2b3fc JN |
101 | pm_runtime_forbid(&pci->dev); |
102 | pm_runtime_get_noresume(&pci->dev); | |
1ed2b3fc JN |
103 | } |
104 | ||
1ed2b3fc JN |
105 | static int dwc_pwm_suspend(struct device *dev) |
106 | { | |
a2ac1cbc RJ |
107 | struct dwc_pwm_drvdata *ddata = dev_get_drvdata(dev); |
108 | unsigned int idx; | |
109 | ||
110 | for (idx = 0; idx < ddata->info->nr; idx++) { | |
111 | struct pwm_chip *chip = ddata->chips[idx]; | |
112 | struct dwc_pwm *dwc = to_dwc_pwm(chip); | |
113 | unsigned int i; | |
114 | ||
115 | for (i = 0; i < DWC_TIMERS_TOTAL; i++) { | |
116 | if (chip->pwms[i].state.enabled) { | |
117 | dev_err(dev, "PWM %u in use by consumer (%s)\n", | |
118 | i, chip->pwms[i].label); | |
119 | return -EBUSY; | |
120 | } | |
121 | dwc->ctx[i].cnt = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(i)); | |
122 | dwc->ctx[i].cnt2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(i)); | |
123 | dwc->ctx[i].ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(i)); | |
1ed2b3fc | 124 | } |
1ed2b3fc JN |
125 | } |
126 | ||
127 | return 0; | |
128 | } | |
129 | ||
130 | static int dwc_pwm_resume(struct device *dev) | |
131 | { | |
a2ac1cbc RJ |
132 | struct dwc_pwm_drvdata *ddata = dev_get_drvdata(dev); |
133 | unsigned int idx; | |
134 | ||
135 | for (idx = 0; idx < ddata->info->nr; idx++) { | |
136 | struct pwm_chip *chip = ddata->chips[idx]; | |
137 | struct dwc_pwm *dwc = to_dwc_pwm(chip); | |
138 | unsigned int i; | |
139 | ||
140 | for (i = 0; i < DWC_TIMERS_TOTAL; i++) { | |
141 | dwc_pwm_writel(dwc, dwc->ctx[i].cnt, DWC_TIM_LD_CNT(i)); | |
142 | dwc_pwm_writel(dwc, dwc->ctx[i].cnt2, DWC_TIM_LD_CNT2(i)); | |
143 | dwc_pwm_writel(dwc, dwc->ctx[i].ctrl, DWC_TIM_CTRL(i)); | |
144 | } | |
1ed2b3fc JN |
145 | } |
146 | ||
147 | return 0; | |
148 | } | |
1ed2b3fc | 149 | |
30b5b066 | 150 | static DEFINE_SIMPLE_DEV_PM_OPS(dwc_pwm_pm_ops, dwc_pwm_suspend, dwc_pwm_resume); |
1ed2b3fc JN |
151 | |
152 | static const struct pci_device_id dwc_pwm_id_table[] = { | |
ebf2c89e | 153 | { PCI_VDEVICE(INTEL, 0x4bb7), (kernel_ulong_t)&ehl_pwm_info }, |
1ed2b3fc JN |
154 | { } /* Terminating Entry */ |
155 | }; | |
156 | MODULE_DEVICE_TABLE(pci, dwc_pwm_id_table); | |
157 | ||
158 | static struct pci_driver dwc_pwm_driver = { | |
159 | .name = "pwm-dwc", | |
160 | .probe = dwc_pwm_probe, | |
161 | .remove = dwc_pwm_remove, | |
162 | .id_table = dwc_pwm_id_table, | |
163 | .driver = { | |
7cfce2b8 | 164 | .pm = pm_sleep_ptr(&dwc_pwm_pm_ops), |
1ed2b3fc JN |
165 | }, |
166 | }; | |
167 | ||
168 | module_pci_driver(dwc_pwm_driver); | |
169 | ||
170 | MODULE_AUTHOR("Felipe Balbi (Intel)"); | |
171 | MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>"); | |
172 | MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>"); | |
173 | MODULE_DESCRIPTION("DesignWare PWM Controller"); | |
174 | MODULE_LICENSE("GPL"); |