2 * Copyright 2017 Google Inc
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version
7 * 2 of the License, or (at your option) any later version.
9 * Provides a simple driver to control the ASPEED LPC snoop interface which
10 * allows the BMC to listen on and save the data written by
11 * the host to an arbitrary LPC I/O port.
13 * Typically used by the BMC to "watch" host boot progress via port
14 * 0x80 writes made by the BIOS during the boot process.
17 #include <linux/bitops.h>
18 #include <linux/interrupt.h>
20 #include <linux/kfifo.h>
21 #include <linux/mfd/syscon.h>
22 #include <linux/miscdevice.h>
23 #include <linux/module.h>
25 #include <linux/of_device.h>
26 #include <linux/platform_device.h>
27 #include <linux/poll.h>
28 #include <linux/regmap.h>
30 #define DEVICE_NAME "aspeed-lpc-snoop"
32 #define NUM_SNOOP_CHANNELS 2
33 #define SNOOP_FIFO_SIZE 2048
36 #define HICR5_EN_SNP0W BIT(0)
37 #define HICR5_ENINT_SNP0W BIT(1)
38 #define HICR5_EN_SNP1W BIT(2)
39 #define HICR5_ENINT_SNP1W BIT(3)
42 #define HICR6_STR_SNP0W BIT(0)
43 #define HICR6_STR_SNP1W BIT(1)
45 #define SNPWADR_CH0_MASK GENMASK(15, 0)
46 #define SNPWADR_CH0_SHIFT 0
47 #define SNPWADR_CH1_MASK GENMASK(31, 16)
48 #define SNPWADR_CH1_SHIFT 16
50 #define SNPWDR_CH0_MASK GENMASK(7, 0)
51 #define SNPWDR_CH0_SHIFT 0
52 #define SNPWDR_CH1_MASK GENMASK(15, 8)
53 #define SNPWDR_CH1_SHIFT 8
55 #define HICRB_ENSNP0D BIT(14)
56 #define HICRB_ENSNP1D BIT(15)
58 struct aspeed_lpc_snoop_model_data
{
59 /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500
62 unsigned int has_hicrb_ensnp
;
65 struct aspeed_lpc_snoop_channel
{
68 struct miscdevice miscdev
;
71 struct aspeed_lpc_snoop
{
72 struct regmap
*regmap
;
74 struct aspeed_lpc_snoop_channel chan
[NUM_SNOOP_CHANNELS
];
77 static struct aspeed_lpc_snoop_channel
*snoop_file_to_chan(struct file
*file
)
79 return container_of(file
->private_data
,
80 struct aspeed_lpc_snoop_channel
,
84 static ssize_t
snoop_file_read(struct file
*file
, char __user
*buffer
,
85 size_t count
, loff_t
*ppos
)
87 struct aspeed_lpc_snoop_channel
*chan
= snoop_file_to_chan(file
);
91 if (kfifo_is_empty(&chan
->fifo
)) {
92 if (file
->f_flags
& O_NONBLOCK
)
94 ret
= wait_event_interruptible(chan
->wq
,
95 !kfifo_is_empty(&chan
->fifo
));
96 if (ret
== -ERESTARTSYS
)
99 ret
= kfifo_to_user(&chan
->fifo
, buffer
, count
, &copied
);
101 return ret
? ret
: copied
;
104 static unsigned int snoop_file_poll(struct file
*file
,
105 struct poll_table_struct
*pt
)
107 struct aspeed_lpc_snoop_channel
*chan
= snoop_file_to_chan(file
);
109 poll_wait(file
, &chan
->wq
, pt
);
110 return !kfifo_is_empty(&chan
->fifo
) ? POLLIN
: 0;
113 static const struct file_operations snoop_fops
= {
114 .owner
= THIS_MODULE
,
115 .read
= snoop_file_read
,
116 .poll
= snoop_file_poll
,
117 .llseek
= noop_llseek
,
120 /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
121 static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel
*chan
, u8 val
)
123 if (!kfifo_initialized(&chan
->fifo
))
125 if (kfifo_is_full(&chan
->fifo
))
126 kfifo_skip(&chan
->fifo
);
127 kfifo_put(&chan
->fifo
, val
);
128 wake_up_interruptible(&chan
->wq
);
131 static irqreturn_t
aspeed_lpc_snoop_irq(int irq
, void *arg
)
133 struct aspeed_lpc_snoop
*lpc_snoop
= arg
;
136 if (regmap_read(lpc_snoop
->regmap
, HICR6
, ®
))
139 /* Check if one of the snoop channels is interrupting */
140 reg
&= (HICR6_STR_SNP0W
| HICR6_STR_SNP1W
);
144 /* Ack pending IRQs */
145 regmap_write(lpc_snoop
->regmap
, HICR6
, reg
);
147 /* Read and save most recent snoop'ed data byte to FIFO */
148 regmap_read(lpc_snoop
->regmap
, SNPWDR
, &data
);
150 if (reg
& HICR6_STR_SNP0W
) {
151 u8 val
= (data
& SNPWDR_CH0_MASK
) >> SNPWDR_CH0_SHIFT
;
153 put_fifo_with_discard(&lpc_snoop
->chan
[0], val
);
155 if (reg
& HICR6_STR_SNP1W
) {
156 u8 val
= (data
& SNPWDR_CH1_MASK
) >> SNPWDR_CH1_SHIFT
;
158 put_fifo_with_discard(&lpc_snoop
->chan
[1], val
);
164 static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop
*lpc_snoop
,
165 struct platform_device
*pdev
)
167 struct device
*dev
= &pdev
->dev
;
170 lpc_snoop
->irq
= platform_get_irq(pdev
, 0);
174 rc
= devm_request_irq(dev
, lpc_snoop
->irq
,
175 aspeed_lpc_snoop_irq
, IRQF_SHARED
,
176 DEVICE_NAME
, lpc_snoop
);
178 dev_warn(dev
, "Unable to request IRQ %d\n", lpc_snoop
->irq
);
186 static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
188 int channel
, u16 lpc_port
)
191 u32 hicr5_en
, snpwadr_mask
, snpwadr_shift
, hicrb_en
;
192 const struct aspeed_lpc_snoop_model_data
*model_data
=
193 of_device_get_match_data(dev
);
195 init_waitqueue_head(&lpc_snoop
->chan
[channel
].wq
);
196 /* Create FIFO datastructure */
197 rc
= kfifo_alloc(&lpc_snoop
->chan
[channel
].fifo
,
198 SNOOP_FIFO_SIZE
, GFP_KERNEL
);
202 lpc_snoop
->chan
[channel
].miscdev
.minor
= MISC_DYNAMIC_MINOR
;
203 lpc_snoop
->chan
[channel
].miscdev
.name
=
204 devm_kasprintf(dev
, GFP_KERNEL
, "%s%d", DEVICE_NAME
, channel
);
205 lpc_snoop
->chan
[channel
].miscdev
.fops
= &snoop_fops
;
206 lpc_snoop
->chan
[channel
].miscdev
.parent
= dev
;
207 rc
= misc_register(&lpc_snoop
->chan
[channel
].miscdev
);
211 /* Enable LPC snoop channel at requested port */
214 hicr5_en
= HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
;
215 snpwadr_mask
= SNPWADR_CH0_MASK
;
216 snpwadr_shift
= SNPWADR_CH0_SHIFT
;
217 hicrb_en
= HICRB_ENSNP0D
;
220 hicr5_en
= HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
;
221 snpwadr_mask
= SNPWADR_CH1_MASK
;
222 snpwadr_shift
= SNPWADR_CH1_SHIFT
;
223 hicrb_en
= HICRB_ENSNP1D
;
229 regmap_update_bits(lpc_snoop
->regmap
, HICR5
, hicr5_en
, hicr5_en
);
230 regmap_update_bits(lpc_snoop
->regmap
, SNPWADR
, snpwadr_mask
,
231 lpc_port
<< snpwadr_shift
);
232 if (model_data
->has_hicrb_ensnp
)
233 regmap_update_bits(lpc_snoop
->regmap
, HICRB
,
239 static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
244 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
245 HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
,
249 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
250 HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
,
257 kfifo_free(&lpc_snoop
->chan
[channel
].fifo
);
258 misc_deregister(&lpc_snoop
->chan
[channel
].miscdev
);
261 static int aspeed_lpc_snoop_probe(struct platform_device
*pdev
)
263 struct aspeed_lpc_snoop
*lpc_snoop
;
270 lpc_snoop
= devm_kzalloc(dev
, sizeof(*lpc_snoop
), GFP_KERNEL
);
274 lpc_snoop
->regmap
= syscon_node_to_regmap(
275 pdev
->dev
.parent
->of_node
);
276 if (IS_ERR(lpc_snoop
->regmap
)) {
277 dev_err(dev
, "Couldn't get regmap\n");
281 dev_set_drvdata(&pdev
->dev
, lpc_snoop
);
283 rc
= of_property_read_u32_index(dev
->of_node
, "snoop-ports", 0, &port
);
285 dev_err(dev
, "no snoop ports configured\n");
289 rc
= aspeed_lpc_snoop_config_irq(lpc_snoop
, pdev
);
293 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, dev
, 0, port
);
297 /* Configuration of 2nd snoop channel port is optional */
298 if (of_property_read_u32_index(dev
->of_node
, "snoop-ports",
300 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, dev
, 1, port
);
302 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
308 static int aspeed_lpc_snoop_remove(struct platform_device
*pdev
)
310 struct aspeed_lpc_snoop
*lpc_snoop
= dev_get_drvdata(&pdev
->dev
);
312 /* Disable both snoop channels */
313 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
314 aspeed_lpc_disable_snoop(lpc_snoop
, 1);
319 static const struct aspeed_lpc_snoop_model_data ast2400_model_data
= {
320 .has_hicrb_ensnp
= 0,
323 static const struct aspeed_lpc_snoop_model_data ast2500_model_data
= {
324 .has_hicrb_ensnp
= 1,
327 static const struct of_device_id aspeed_lpc_snoop_match
[] = {
328 { .compatible
= "aspeed,ast2400-lpc-snoop",
329 .data
= &ast2400_model_data
},
330 { .compatible
= "aspeed,ast2500-lpc-snoop",
331 .data
= &ast2500_model_data
},
335 static struct platform_driver aspeed_lpc_snoop_driver
= {
338 .of_match_table
= aspeed_lpc_snoop_match
,
340 .probe
= aspeed_lpc_snoop_probe
,
341 .remove
= aspeed_lpc_snoop_remove
,
344 module_platform_driver(aspeed_lpc_snoop_driver
);
346 MODULE_DEVICE_TABLE(of
, aspeed_lpc_snoop_match
);
347 MODULE_LICENSE("GPL");
348 MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
349 MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");