1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright 2017 Google Inc
5 * Provides a simple driver to control the ASPEED LPC snoop interface which
6 * allows the BMC to listen on and save the data written by
7 * the host to an arbitrary LPC I/O port.
9 * Typically used by the BMC to "watch" host boot progress via port
10 * 0x80 writes made by the BIOS during the boot process.
13 #include <linux/bitops.h>
14 #include <linux/clk.h>
15 #include <linux/interrupt.h>
17 #include <linux/kfifo.h>
18 #include <linux/mfd/syscon.h>
19 #include <linux/miscdevice.h>
20 #include <linux/module.h>
22 #include <linux/platform_device.h>
23 #include <linux/poll.h>
24 #include <linux/regmap.h>
26 #define DEVICE_NAME "aspeed-lpc-snoop"
28 #define NUM_SNOOP_CHANNELS 2
29 #define SNOOP_FIFO_SIZE 2048
32 #define HICR5_EN_SNP0W BIT(0)
33 #define HICR5_ENINT_SNP0W BIT(1)
34 #define HICR5_EN_SNP1W BIT(2)
35 #define HICR5_ENINT_SNP1W BIT(3)
37 #define HICR6_STR_SNP0W BIT(0)
38 #define HICR6_STR_SNP1W BIT(1)
40 #define SNPWADR_CH0_MASK GENMASK(15, 0)
41 #define SNPWADR_CH0_SHIFT 0
42 #define SNPWADR_CH1_MASK GENMASK(31, 16)
43 #define SNPWADR_CH1_SHIFT 16
45 #define SNPWDR_CH0_MASK GENMASK(7, 0)
46 #define SNPWDR_CH0_SHIFT 0
47 #define SNPWDR_CH1_MASK GENMASK(15, 8)
48 #define SNPWDR_CH1_SHIFT 8
50 #define HICRB_ENSNP0D BIT(14)
51 #define HICRB_ENSNP1D BIT(15)
53 struct aspeed_lpc_snoop_model_data
{
54 /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500
57 unsigned int has_hicrb_ensnp
;
60 struct aspeed_lpc_snoop_channel
{
63 struct miscdevice miscdev
;
66 struct aspeed_lpc_snoop
{
67 struct regmap
*regmap
;
70 struct aspeed_lpc_snoop_channel chan
[NUM_SNOOP_CHANNELS
];
73 static struct aspeed_lpc_snoop_channel
*snoop_file_to_chan(struct file
*file
)
75 return container_of(file
->private_data
,
76 struct aspeed_lpc_snoop_channel
,
80 static ssize_t
snoop_file_read(struct file
*file
, char __user
*buffer
,
81 size_t count
, loff_t
*ppos
)
83 struct aspeed_lpc_snoop_channel
*chan
= snoop_file_to_chan(file
);
87 if (kfifo_is_empty(&chan
->fifo
)) {
88 if (file
->f_flags
& O_NONBLOCK
)
90 ret
= wait_event_interruptible(chan
->wq
,
91 !kfifo_is_empty(&chan
->fifo
));
92 if (ret
== -ERESTARTSYS
)
95 ret
= kfifo_to_user(&chan
->fifo
, buffer
, count
, &copied
);
102 static __poll_t
snoop_file_poll(struct file
*file
,
103 struct poll_table_struct
*pt
)
105 struct aspeed_lpc_snoop_channel
*chan
= snoop_file_to_chan(file
);
107 poll_wait(file
, &chan
->wq
, pt
);
108 return !kfifo_is_empty(&chan
->fifo
) ? EPOLLIN
: 0;
111 static const struct file_operations snoop_fops
= {
112 .owner
= THIS_MODULE
,
113 .read
= snoop_file_read
,
114 .poll
= snoop_file_poll
,
115 .llseek
= noop_llseek
,
118 /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
119 static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel
*chan
, u8 val
)
121 if (!kfifo_initialized(&chan
->fifo
))
123 if (kfifo_is_full(&chan
->fifo
))
124 kfifo_skip(&chan
->fifo
);
125 kfifo_put(&chan
->fifo
, val
);
126 wake_up_interruptible(&chan
->wq
);
129 static irqreturn_t
aspeed_lpc_snoop_irq(int irq
, void *arg
)
131 struct aspeed_lpc_snoop
*lpc_snoop
= arg
;
134 if (regmap_read(lpc_snoop
->regmap
, HICR6
, ®
))
137 /* Check if one of the snoop channels is interrupting */
138 reg
&= (HICR6_STR_SNP0W
| HICR6_STR_SNP1W
);
142 /* Ack pending IRQs */
143 regmap_write(lpc_snoop
->regmap
, HICR6
, reg
);
145 /* Read and save most recent snoop'ed data byte to FIFO */
146 regmap_read(lpc_snoop
->regmap
, SNPWDR
, &data
);
148 if (reg
& HICR6_STR_SNP0W
) {
149 u8 val
= (data
& SNPWDR_CH0_MASK
) >> SNPWDR_CH0_SHIFT
;
151 put_fifo_with_discard(&lpc_snoop
->chan
[0], val
);
153 if (reg
& HICR6_STR_SNP1W
) {
154 u8 val
= (data
& SNPWDR_CH1_MASK
) >> SNPWDR_CH1_SHIFT
;
156 put_fifo_with_discard(&lpc_snoop
->chan
[1], val
);
162 static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop
*lpc_snoop
,
163 struct platform_device
*pdev
)
165 struct device
*dev
= &pdev
->dev
;
168 lpc_snoop
->irq
= platform_get_irq(pdev
, 0);
172 rc
= devm_request_irq(dev
, lpc_snoop
->irq
,
173 aspeed_lpc_snoop_irq
, IRQF_SHARED
,
174 DEVICE_NAME
, lpc_snoop
);
176 dev_warn(dev
, "Unable to request IRQ %d\n", lpc_snoop
->irq
);
184 static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
186 int channel
, u16 lpc_port
)
189 u32 hicr5_en
, snpwadr_mask
, snpwadr_shift
, hicrb_en
;
190 const struct aspeed_lpc_snoop_model_data
*model_data
=
191 of_device_get_match_data(dev
);
193 init_waitqueue_head(&lpc_snoop
->chan
[channel
].wq
);
194 /* Create FIFO datastructure */
195 rc
= kfifo_alloc(&lpc_snoop
->chan
[channel
].fifo
,
196 SNOOP_FIFO_SIZE
, GFP_KERNEL
);
200 lpc_snoop
->chan
[channel
].miscdev
.minor
= MISC_DYNAMIC_MINOR
;
201 lpc_snoop
->chan
[channel
].miscdev
.name
=
202 devm_kasprintf(dev
, GFP_KERNEL
, "%s%d", DEVICE_NAME
, channel
);
203 lpc_snoop
->chan
[channel
].miscdev
.fops
= &snoop_fops
;
204 lpc_snoop
->chan
[channel
].miscdev
.parent
= dev
;
205 rc
= misc_register(&lpc_snoop
->chan
[channel
].miscdev
);
209 /* Enable LPC snoop channel at requested port */
212 hicr5_en
= HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
;
213 snpwadr_mask
= SNPWADR_CH0_MASK
;
214 snpwadr_shift
= SNPWADR_CH0_SHIFT
;
215 hicrb_en
= HICRB_ENSNP0D
;
218 hicr5_en
= HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
;
219 snpwadr_mask
= SNPWADR_CH1_MASK
;
220 snpwadr_shift
= SNPWADR_CH1_SHIFT
;
221 hicrb_en
= HICRB_ENSNP1D
;
227 regmap_update_bits(lpc_snoop
->regmap
, HICR5
, hicr5_en
, hicr5_en
);
228 regmap_update_bits(lpc_snoop
->regmap
, SNPWADR
, snpwadr_mask
,
229 lpc_port
<< snpwadr_shift
);
230 if (model_data
->has_hicrb_ensnp
)
231 regmap_update_bits(lpc_snoop
->regmap
, HICRB
,
237 static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
242 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
243 HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
,
247 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
248 HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
,
255 kfifo_free(&lpc_snoop
->chan
[channel
].fifo
);
256 misc_deregister(&lpc_snoop
->chan
[channel
].miscdev
);
259 static int aspeed_lpc_snoop_probe(struct platform_device
*pdev
)
261 struct aspeed_lpc_snoop
*lpc_snoop
;
263 struct device_node
*np
;
269 lpc_snoop
= devm_kzalloc(dev
, sizeof(*lpc_snoop
), GFP_KERNEL
);
273 np
= pdev
->dev
.parent
->of_node
;
274 if (!of_device_is_compatible(np
, "aspeed,ast2400-lpc-v2") &&
275 !of_device_is_compatible(np
, "aspeed,ast2500-lpc-v2") &&
276 !of_device_is_compatible(np
, "aspeed,ast2600-lpc-v2")) {
277 dev_err(dev
, "unsupported LPC device binding\n");
281 lpc_snoop
->regmap
= syscon_node_to_regmap(np
);
282 if (IS_ERR(lpc_snoop
->regmap
)) {
283 dev_err(dev
, "Couldn't get regmap\n");
287 dev_set_drvdata(&pdev
->dev
, lpc_snoop
);
289 rc
= of_property_read_u32_index(dev
->of_node
, "snoop-ports", 0, &port
);
291 dev_err(dev
, "no snoop ports configured\n");
295 lpc_snoop
->clk
= devm_clk_get(dev
, NULL
);
296 if (IS_ERR(lpc_snoop
->clk
)) {
297 rc
= PTR_ERR(lpc_snoop
->clk
);
298 if (rc
!= -EPROBE_DEFER
)
299 dev_err(dev
, "couldn't get clock\n");
302 rc
= clk_prepare_enable(lpc_snoop
->clk
);
304 dev_err(dev
, "couldn't enable clock\n");
308 rc
= aspeed_lpc_snoop_config_irq(lpc_snoop
, pdev
);
312 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, dev
, 0, port
);
316 /* Configuration of 2nd snoop channel port is optional */
317 if (of_property_read_u32_index(dev
->of_node
, "snoop-ports",
319 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, dev
, 1, port
);
321 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
329 clk_disable_unprepare(lpc_snoop
->clk
);
334 static void aspeed_lpc_snoop_remove(struct platform_device
*pdev
)
336 struct aspeed_lpc_snoop
*lpc_snoop
= dev_get_drvdata(&pdev
->dev
);
338 /* Disable both snoop channels */
339 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
340 aspeed_lpc_disable_snoop(lpc_snoop
, 1);
342 clk_disable_unprepare(lpc_snoop
->clk
);
345 static const struct aspeed_lpc_snoop_model_data ast2400_model_data
= {
346 .has_hicrb_ensnp
= 0,
349 static const struct aspeed_lpc_snoop_model_data ast2500_model_data
= {
350 .has_hicrb_ensnp
= 1,
353 static const struct of_device_id aspeed_lpc_snoop_match
[] = {
354 { .compatible
= "aspeed,ast2400-lpc-snoop",
355 .data
= &ast2400_model_data
},
356 { .compatible
= "aspeed,ast2500-lpc-snoop",
357 .data
= &ast2500_model_data
},
358 { .compatible
= "aspeed,ast2600-lpc-snoop",
359 .data
= &ast2500_model_data
},
363 static struct platform_driver aspeed_lpc_snoop_driver
= {
366 .of_match_table
= aspeed_lpc_snoop_match
,
368 .probe
= aspeed_lpc_snoop_probe
,
369 .remove
= aspeed_lpc_snoop_remove
,
372 module_platform_driver(aspeed_lpc_snoop_driver
);
374 MODULE_DEVICE_TABLE(of
, aspeed_lpc_snoop_match
);
375 MODULE_LICENSE("GPL");
376 MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
377 MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");