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/interrupt.h>
16 #include <linux/kfifo.h>
17 #include <linux/mfd/syscon.h>
18 #include <linux/miscdevice.h>
19 #include <linux/module.h>
21 #include <linux/of_device.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)
38 #define HICR6_STR_SNP0W BIT(0)
39 #define HICR6_STR_SNP1W BIT(1)
41 #define SNPWADR_CH0_MASK GENMASK(15, 0)
42 #define SNPWADR_CH0_SHIFT 0
43 #define SNPWADR_CH1_MASK GENMASK(31, 16)
44 #define SNPWADR_CH1_SHIFT 16
46 #define SNPWDR_CH0_MASK GENMASK(7, 0)
47 #define SNPWDR_CH0_SHIFT 0
48 #define SNPWDR_CH1_MASK GENMASK(15, 8)
49 #define SNPWDR_CH1_SHIFT 8
51 #define HICRB_ENSNP0D BIT(14)
52 #define HICRB_ENSNP1D BIT(15)
54 struct aspeed_lpc_snoop_model_data
{
55 /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500
58 unsigned int has_hicrb_ensnp
;
61 struct aspeed_lpc_snoop_channel
{
64 struct miscdevice miscdev
;
67 struct aspeed_lpc_snoop
{
68 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
);
97 return ret
? ret
: copied
;
100 static __poll_t
snoop_file_poll(struct file
*file
,
101 struct poll_table_struct
*pt
)
103 struct aspeed_lpc_snoop_channel
*chan
= snoop_file_to_chan(file
);
105 poll_wait(file
, &chan
->wq
, pt
);
106 return !kfifo_is_empty(&chan
->fifo
) ? EPOLLIN
: 0;
109 static const struct file_operations snoop_fops
= {
110 .owner
= THIS_MODULE
,
111 .read
= snoop_file_read
,
112 .poll
= snoop_file_poll
,
113 .llseek
= noop_llseek
,
116 /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
117 static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel
*chan
, u8 val
)
119 if (!kfifo_initialized(&chan
->fifo
))
121 if (kfifo_is_full(&chan
->fifo
))
122 kfifo_skip(&chan
->fifo
);
123 kfifo_put(&chan
->fifo
, val
);
124 wake_up_interruptible(&chan
->wq
);
127 static irqreturn_t
aspeed_lpc_snoop_irq(int irq
, void *arg
)
129 struct aspeed_lpc_snoop
*lpc_snoop
= arg
;
132 if (regmap_read(lpc_snoop
->regmap
, HICR6
, ®
))
135 /* Check if one of the snoop channels is interrupting */
136 reg
&= (HICR6_STR_SNP0W
| HICR6_STR_SNP1W
);
140 /* Ack pending IRQs */
141 regmap_write(lpc_snoop
->regmap
, HICR6
, reg
);
143 /* Read and save most recent snoop'ed data byte to FIFO */
144 regmap_read(lpc_snoop
->regmap
, SNPWDR
, &data
);
146 if (reg
& HICR6_STR_SNP0W
) {
147 u8 val
= (data
& SNPWDR_CH0_MASK
) >> SNPWDR_CH0_SHIFT
;
149 put_fifo_with_discard(&lpc_snoop
->chan
[0], val
);
151 if (reg
& HICR6_STR_SNP1W
) {
152 u8 val
= (data
& SNPWDR_CH1_MASK
) >> SNPWDR_CH1_SHIFT
;
154 put_fifo_with_discard(&lpc_snoop
->chan
[1], val
);
160 static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop
*lpc_snoop
,
161 struct platform_device
*pdev
)
163 struct device
*dev
= &pdev
->dev
;
166 lpc_snoop
->irq
= platform_get_irq(pdev
, 0);
170 rc
= devm_request_irq(dev
, lpc_snoop
->irq
,
171 aspeed_lpc_snoop_irq
, IRQF_SHARED
,
172 DEVICE_NAME
, lpc_snoop
);
174 dev_warn(dev
, "Unable to request IRQ %d\n", lpc_snoop
->irq
);
182 static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
184 int channel
, u16 lpc_port
)
187 u32 hicr5_en
, snpwadr_mask
, snpwadr_shift
, hicrb_en
;
188 const struct aspeed_lpc_snoop_model_data
*model_data
=
189 of_device_get_match_data(dev
);
191 init_waitqueue_head(&lpc_snoop
->chan
[channel
].wq
);
192 /* Create FIFO datastructure */
193 rc
= kfifo_alloc(&lpc_snoop
->chan
[channel
].fifo
,
194 SNOOP_FIFO_SIZE
, GFP_KERNEL
);
198 lpc_snoop
->chan
[channel
].miscdev
.minor
= MISC_DYNAMIC_MINOR
;
199 lpc_snoop
->chan
[channel
].miscdev
.name
=
200 devm_kasprintf(dev
, GFP_KERNEL
, "%s%d", DEVICE_NAME
, channel
);
201 lpc_snoop
->chan
[channel
].miscdev
.fops
= &snoop_fops
;
202 lpc_snoop
->chan
[channel
].miscdev
.parent
= dev
;
203 rc
= misc_register(&lpc_snoop
->chan
[channel
].miscdev
);
207 /* Enable LPC snoop channel at requested port */
210 hicr5_en
= HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
;
211 snpwadr_mask
= SNPWADR_CH0_MASK
;
212 snpwadr_shift
= SNPWADR_CH0_SHIFT
;
213 hicrb_en
= HICRB_ENSNP0D
;
216 hicr5_en
= HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
;
217 snpwadr_mask
= SNPWADR_CH1_MASK
;
218 snpwadr_shift
= SNPWADR_CH1_SHIFT
;
219 hicrb_en
= HICRB_ENSNP1D
;
225 regmap_update_bits(lpc_snoop
->regmap
, HICR5
, hicr5_en
, hicr5_en
);
226 regmap_update_bits(lpc_snoop
->regmap
, SNPWADR
, snpwadr_mask
,
227 lpc_port
<< snpwadr_shift
);
228 if (model_data
->has_hicrb_ensnp
)
229 regmap_update_bits(lpc_snoop
->regmap
, HICRB
,
235 static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
240 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
241 HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
,
245 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
246 HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
,
253 kfifo_free(&lpc_snoop
->chan
[channel
].fifo
);
254 misc_deregister(&lpc_snoop
->chan
[channel
].miscdev
);
257 static int aspeed_lpc_snoop_probe(struct platform_device
*pdev
)
259 struct aspeed_lpc_snoop
*lpc_snoop
;
266 lpc_snoop
= devm_kzalloc(dev
, sizeof(*lpc_snoop
), GFP_KERNEL
);
270 lpc_snoop
->regmap
= syscon_node_to_regmap(
271 pdev
->dev
.parent
->of_node
);
272 if (IS_ERR(lpc_snoop
->regmap
)) {
273 dev_err(dev
, "Couldn't get regmap\n");
277 dev_set_drvdata(&pdev
->dev
, lpc_snoop
);
279 rc
= of_property_read_u32_index(dev
->of_node
, "snoop-ports", 0, &port
);
281 dev_err(dev
, "no snoop ports configured\n");
285 rc
= aspeed_lpc_snoop_config_irq(lpc_snoop
, pdev
);
289 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, dev
, 0, port
);
293 /* Configuration of 2nd snoop channel port is optional */
294 if (of_property_read_u32_index(dev
->of_node
, "snoop-ports",
296 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, dev
, 1, port
);
298 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
304 static int aspeed_lpc_snoop_remove(struct platform_device
*pdev
)
306 struct aspeed_lpc_snoop
*lpc_snoop
= dev_get_drvdata(&pdev
->dev
);
308 /* Disable both snoop channels */
309 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
310 aspeed_lpc_disable_snoop(lpc_snoop
, 1);
315 static const struct aspeed_lpc_snoop_model_data ast2400_model_data
= {
316 .has_hicrb_ensnp
= 0,
319 static const struct aspeed_lpc_snoop_model_data ast2500_model_data
= {
320 .has_hicrb_ensnp
= 1,
323 static const struct of_device_id aspeed_lpc_snoop_match
[] = {
324 { .compatible
= "aspeed,ast2400-lpc-snoop",
325 .data
= &ast2400_model_data
},
326 { .compatible
= "aspeed,ast2500-lpc-snoop",
327 .data
= &ast2500_model_data
},
328 { .compatible
= "aspeed,ast2600-lpc-snoop",
329 .data
= &ast2500_model_data
},
333 static struct platform_driver aspeed_lpc_snoop_driver
= {
336 .of_match_table
= aspeed_lpc_snoop_match
,
338 .probe
= aspeed_lpc_snoop_probe
,
339 .remove
= aspeed_lpc_snoop_remove
,
342 module_platform_driver(aspeed_lpc_snoop_driver
);
344 MODULE_DEVICE_TABLE(of
, aspeed_lpc_snoop_match
);
345 MODULE_LICENSE("GPL");
346 MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
347 MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");