1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2018, Nuvoton Corporation. |
4 | * Copyright (c) 2018, Intel Corporation. |
5 | */ |
6 | |
7 | #define pr_fmt(fmt) "nuvoton-kcs-bmc: " fmt |
8 | |
9 | #include <linux/atomic.h> |
10 | #include <linux/errno.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/io.h> |
13 | #include <linux/mfd/syscon.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/regmap.h> |
18 | #include <linux/slab.h> |
19 | |
20 | #include "kcs_bmc_device.h" |
21 | |
22 | #define DEVICE_NAME "npcm-kcs-bmc" |
23 | #define KCS_CHANNEL_MAX 3 |
24 | |
25 | #define KCS1ST 0x0C |
26 | #define KCS2ST 0x1E |
27 | #define KCS3ST 0x30 |
28 | |
29 | #define KCS1DO 0x0E |
30 | #define KCS2DO 0x20 |
31 | #define KCS3DO 0x32 |
32 | |
33 | #define KCS1DI 0x10 |
34 | #define KCS2DI 0x22 |
35 | #define KCS3DI 0x34 |
36 | |
37 | #define KCS1CTL 0x18 |
38 | #define KCS2CTL 0x2A |
39 | #define KCS3CTL 0x3C |
40 | #define KCS_CTL_IBFIE BIT(0) |
41 | #define KCS_CTL_OBEIE BIT(1) |
42 | |
43 | #define KCS1IE 0x1C |
44 | #define KCS2IE 0x2E |
45 | #define KCS3IE 0x40 |
46 | #define KCS_IE_IRQE BIT(0) |
47 | #define KCS_IE_HIRQE BIT(3) |
48 | |
49 | /* |
50 | * 7.2.4 Core KCS Registers |
51 | * Registers in this module are 8 bits. An 8-bit register must be accessed |
52 | * by an 8-bit read or write. |
53 | * |
54 | * sts: KCS Channel n Status Register (KCSnST). |
55 | * dob: KCS Channel n Data Out Buffer Register (KCSnDO). |
56 | * dib: KCS Channel n Data In Buffer Register (KCSnDI). |
57 | * ctl: KCS Channel n Control Register (KCSnCTL). |
58 | * ie : KCS Channel n Interrupt Enable Register (KCSnIE). |
59 | */ |
60 | struct npcm7xx_kcs_reg { |
61 | u32 sts; |
62 | u32 dob; |
63 | u32 dib; |
64 | u32 ctl; |
65 | u32 ie; |
66 | }; |
67 | |
68 | struct npcm7xx_kcs_bmc { |
69 | struct kcs_bmc_device kcs_bmc; |
70 | |
71 | struct regmap *map; |
72 | |
73 | const struct npcm7xx_kcs_reg *reg; |
74 | }; |
75 | |
76 | static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = { |
77 | { .sts = KCS1ST, .dob = KCS1DO, .dib = KCS1DI, .ctl = KCS1CTL, .ie = KCS1IE }, |
78 | { .sts = KCS2ST, .dob = KCS2DO, .dib = KCS2DI, .ctl = KCS2CTL, .ie = KCS2IE }, |
79 | { .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE }, |
80 | }; |
81 | |
82 | static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc_device *kcs_bmc) |
83 | { |
84 | return container_of(kcs_bmc, struct npcm7xx_kcs_bmc, kcs_bmc); |
85 | } |
86 | |
87 | static u8 npcm7xx_kcs_inb(struct kcs_bmc_device *kcs_bmc, u32 reg) |
88 | { |
89 | struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc); |
90 | u32 val = 0; |
91 | int rc; |
92 | |
93 | rc = regmap_read(map: priv->map, reg, val: &val); |
94 | WARN(rc != 0, "regmap_read() failed: %d\n" , rc); |
95 | |
96 | return rc == 0 ? (u8)val : 0; |
97 | } |
98 | |
99 | static void npcm7xx_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data) |
100 | { |
101 | struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc); |
102 | int rc; |
103 | |
104 | rc = regmap_write(map: priv->map, reg, val: data); |
105 | WARN(rc != 0, "regmap_write() failed: %d\n" , rc); |
106 | } |
107 | |
108 | static void npcm7xx_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 data) |
109 | { |
110 | struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc); |
111 | int rc; |
112 | |
113 | rc = regmap_update_bits(map: priv->map, reg, mask, val: data); |
114 | WARN(rc != 0, "regmap_update_bits() failed: %d\n" , rc); |
115 | } |
116 | |
117 | static void npcm7xx_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enable) |
118 | { |
119 | struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc); |
120 | |
121 | regmap_update_bits(map: priv->map, reg: priv->reg->ie, KCS_IE_IRQE | KCS_IE_HIRQE, |
122 | val: enable ? KCS_IE_IRQE | KCS_IE_HIRQE : 0); |
123 | } |
124 | |
125 | static void npcm7xx_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state) |
126 | { |
127 | struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc); |
128 | |
129 | if (mask & KCS_BMC_EVENT_TYPE_OBE) |
130 | regmap_update_bits(map: priv->map, reg: priv->reg->ctl, KCS_CTL_OBEIE, |
131 | val: !!(state & KCS_BMC_EVENT_TYPE_OBE) * KCS_CTL_OBEIE); |
132 | |
133 | if (mask & KCS_BMC_EVENT_TYPE_IBF) |
134 | regmap_update_bits(map: priv->map, reg: priv->reg->ctl, KCS_CTL_IBFIE, |
135 | val: !!(state & KCS_BMC_EVENT_TYPE_IBF) * KCS_CTL_IBFIE); |
136 | } |
137 | |
138 | static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg) |
139 | { |
140 | struct kcs_bmc_device *kcs_bmc = arg; |
141 | |
142 | return kcs_bmc_handle_event(kcs_bmc); |
143 | } |
144 | |
145 | static int npcm7xx_kcs_config_irq(struct kcs_bmc_device *kcs_bmc, |
146 | struct platform_device *pdev) |
147 | { |
148 | struct device *dev = &pdev->dev; |
149 | int irq; |
150 | |
151 | irq = platform_get_irq(pdev, 0); |
152 | if (irq < 0) |
153 | return irq; |
154 | |
155 | return devm_request_irq(dev, irq, handler: npcm7xx_kcs_irq, IRQF_SHARED, |
156 | devname: dev_name(dev), dev_id: kcs_bmc); |
157 | } |
158 | |
159 | static const struct kcs_bmc_device_ops npcm7xx_kcs_ops = { |
160 | .irq_mask_update = npcm7xx_kcs_irq_mask_update, |
161 | .io_inputb = npcm7xx_kcs_inb, |
162 | .io_outputb = npcm7xx_kcs_outb, |
163 | .io_updateb = npcm7xx_kcs_updateb, |
164 | }; |
165 | |
166 | static int npcm7xx_kcs_probe(struct platform_device *pdev) |
167 | { |
168 | struct device *dev = &pdev->dev; |
169 | struct npcm7xx_kcs_bmc *priv; |
170 | struct kcs_bmc_device *kcs_bmc; |
171 | u32 chan; |
172 | int rc; |
173 | |
174 | rc = of_property_read_u32(np: dev->of_node, propname: "kcs_chan" , out_value: &chan); |
175 | if (rc != 0 || chan == 0 || chan > KCS_CHANNEL_MAX) { |
176 | dev_err(dev, "no valid 'kcs_chan' configured\n" ); |
177 | return -ENODEV; |
178 | } |
179 | |
180 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
181 | if (!priv) |
182 | return -ENOMEM; |
183 | |
184 | priv->map = syscon_node_to_regmap(np: dev->parent->of_node); |
185 | if (IS_ERR(ptr: priv->map)) { |
186 | dev_err(dev, "Couldn't get regmap\n" ); |
187 | return -ENODEV; |
188 | } |
189 | priv->reg = &npcm7xx_kcs_reg_tbl[chan - 1]; |
190 | |
191 | kcs_bmc = &priv->kcs_bmc; |
192 | kcs_bmc->dev = &pdev->dev; |
193 | kcs_bmc->channel = chan; |
194 | kcs_bmc->ioreg.idr = priv->reg->dib; |
195 | kcs_bmc->ioreg.odr = priv->reg->dob; |
196 | kcs_bmc->ioreg.str = priv->reg->sts; |
197 | kcs_bmc->ops = &npcm7xx_kcs_ops; |
198 | |
199 | platform_set_drvdata(pdev, data: priv); |
200 | |
201 | rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev); |
202 | if (rc) |
203 | return rc; |
204 | |
205 | npcm7xx_kcs_irq_mask_update(kcs_bmc, mask: (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), state: 0); |
206 | npcm7xx_kcs_enable_channel(kcs_bmc, enable: true); |
207 | |
208 | rc = kcs_bmc_add_device(kcs_bmc); |
209 | if (rc) { |
210 | dev_warn(&pdev->dev, "Failed to register channel %d: %d\n" , kcs_bmc->channel, rc); |
211 | return rc; |
212 | } |
213 | |
214 | pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n" , |
215 | chan, |
216 | kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr, kcs_bmc->ioreg.str); |
217 | |
218 | return 0; |
219 | } |
220 | |
221 | static int npcm7xx_kcs_remove(struct platform_device *pdev) |
222 | { |
223 | struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev); |
224 | struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc; |
225 | |
226 | kcs_bmc_remove_device(kcs_bmc); |
227 | |
228 | npcm7xx_kcs_enable_channel(kcs_bmc, enable: false); |
229 | npcm7xx_kcs_irq_mask_update(kcs_bmc, mask: (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), state: 0); |
230 | |
231 | return 0; |
232 | } |
233 | |
234 | static const struct of_device_id npcm_kcs_bmc_match[] = { |
235 | { .compatible = "nuvoton,npcm750-kcs-bmc" }, |
236 | { } |
237 | }; |
238 | MODULE_DEVICE_TABLE(of, npcm_kcs_bmc_match); |
239 | |
240 | static struct platform_driver npcm_kcs_bmc_driver = { |
241 | .driver = { |
242 | .name = DEVICE_NAME, |
243 | .of_match_table = npcm_kcs_bmc_match, |
244 | }, |
245 | .probe = npcm7xx_kcs_probe, |
246 | .remove = npcm7xx_kcs_remove, |
247 | }; |
248 | module_platform_driver(npcm_kcs_bmc_driver); |
249 | |
250 | MODULE_LICENSE("GPL v2" ); |
251 | MODULE_AUTHOR("Avi Fishman <avifishman70@gmail.com>" ); |
252 | MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>" ); |
253 | MODULE_DESCRIPTION("NPCM7xx device interface to the KCS BMC device" ); |
254 | |