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>
19 #include <linux/kfifo.h>
20 #include <linux/mfd/syscon.h>
21 #include <linux/module.h>
23 #include <linux/of_device.h>
24 #include <linux/platform_device.h>
25 #include <linux/regmap.h>
27 #define DEVICE_NAME "aspeed-lpc-snoop"
29 #define NUM_SNOOP_CHANNELS 2
30 #define SNOOP_FIFO_SIZE 2048
33 #define HICR5_EN_SNP0W BIT(0)
34 #define HICR5_ENINT_SNP0W BIT(1)
35 #define HICR5_EN_SNP1W BIT(2)
36 #define HICR5_ENINT_SNP1W BIT(3)
39 #define HICR6_STR_SNP0W BIT(0)
40 #define HICR6_STR_SNP1W BIT(1)
42 #define SNPWADR_CH0_MASK GENMASK(15, 0)
43 #define SNPWADR_CH0_SHIFT 0
44 #define SNPWADR_CH1_MASK GENMASK(31, 16)
45 #define SNPWADR_CH1_SHIFT 16
47 #define SNPWDR_CH0_MASK GENMASK(7, 0)
48 #define SNPWDR_CH0_SHIFT 0
49 #define SNPWDR_CH1_MASK GENMASK(15, 8)
50 #define SNPWDR_CH1_SHIFT 8
52 #define HICRB_ENSNP0D BIT(14)
53 #define HICRB_ENSNP1D BIT(15)
55 struct aspeed_lpc_snoop_model_data
{
56 /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500
59 unsigned int has_hicrb_ensnp
;
62 struct aspeed_lpc_snoop
{
63 struct regmap
*regmap
;
65 struct kfifo snoop_fifo
[NUM_SNOOP_CHANNELS
];
68 /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
69 static void put_fifo_with_discard(struct kfifo
*fifo
, u8 val
)
71 if (!kfifo_initialized(fifo
))
73 if (kfifo_is_full(fifo
))
78 static irqreturn_t
aspeed_lpc_snoop_irq(int irq
, void *arg
)
80 struct aspeed_lpc_snoop
*lpc_snoop
= arg
;
83 if (regmap_read(lpc_snoop
->regmap
, HICR6
, ®
))
86 /* Check if one of the snoop channels is interrupting */
87 reg
&= (HICR6_STR_SNP0W
| HICR6_STR_SNP1W
);
91 /* Ack pending IRQs */
92 regmap_write(lpc_snoop
->regmap
, HICR6
, reg
);
94 /* Read and save most recent snoop'ed data byte to FIFO */
95 regmap_read(lpc_snoop
->regmap
, SNPWDR
, &data
);
97 if (reg
& HICR6_STR_SNP0W
) {
98 u8 val
= (data
& SNPWDR_CH0_MASK
) >> SNPWDR_CH0_SHIFT
;
100 put_fifo_with_discard(&lpc_snoop
->snoop_fifo
[0], val
);
102 if (reg
& HICR6_STR_SNP1W
) {
103 u8 val
= (data
& SNPWDR_CH1_MASK
) >> SNPWDR_CH1_SHIFT
;
105 put_fifo_with_discard(&lpc_snoop
->snoop_fifo
[1], val
);
111 static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop
*lpc_snoop
,
112 struct platform_device
*pdev
)
114 struct device
*dev
= &pdev
->dev
;
117 lpc_snoop
->irq
= platform_get_irq(pdev
, 0);
121 rc
= devm_request_irq(dev
, lpc_snoop
->irq
,
122 aspeed_lpc_snoop_irq
, IRQF_SHARED
,
123 DEVICE_NAME
, lpc_snoop
);
125 dev_warn(dev
, "Unable to request IRQ %d\n", lpc_snoop
->irq
);
133 static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
135 int channel
, u16 lpc_port
)
138 u32 hicr5_en
, snpwadr_mask
, snpwadr_shift
, hicrb_en
;
139 const struct aspeed_lpc_snoop_model_data
*model_data
=
140 of_device_get_match_data(dev
);
142 /* Create FIFO datastructure */
143 rc
= kfifo_alloc(&lpc_snoop
->snoop_fifo
[channel
],
144 SNOOP_FIFO_SIZE
, GFP_KERNEL
);
148 /* Enable LPC snoop channel at requested port */
151 hicr5_en
= HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
;
152 snpwadr_mask
= SNPWADR_CH0_MASK
;
153 snpwadr_shift
= SNPWADR_CH0_SHIFT
;
154 hicrb_en
= HICRB_ENSNP0D
;
157 hicr5_en
= HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
;
158 snpwadr_mask
= SNPWADR_CH1_MASK
;
159 snpwadr_shift
= SNPWADR_CH1_SHIFT
;
160 hicrb_en
= HICRB_ENSNP1D
;
166 regmap_update_bits(lpc_snoop
->regmap
, HICR5
, hicr5_en
, hicr5_en
);
167 regmap_update_bits(lpc_snoop
->regmap
, SNPWADR
, snpwadr_mask
,
168 lpc_port
<< snpwadr_shift
);
169 if (model_data
->has_hicrb_ensnp
)
170 regmap_update_bits(lpc_snoop
->regmap
, HICRB
,
176 static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
181 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
182 HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
,
186 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
187 HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
,
194 kfifo_free(&lpc_snoop
->snoop_fifo
[channel
]);
197 static int aspeed_lpc_snoop_probe(struct platform_device
*pdev
)
199 struct aspeed_lpc_snoop
*lpc_snoop
;
206 lpc_snoop
= devm_kzalloc(dev
, sizeof(*lpc_snoop
), GFP_KERNEL
);
210 lpc_snoop
->regmap
= syscon_node_to_regmap(
211 pdev
->dev
.parent
->of_node
);
212 if (IS_ERR(lpc_snoop
->regmap
)) {
213 dev_err(dev
, "Couldn't get regmap\n");
217 dev_set_drvdata(&pdev
->dev
, lpc_snoop
);
219 rc
= of_property_read_u32_index(dev
->of_node
, "snoop-ports", 0, &port
);
221 dev_err(dev
, "no snoop ports configured\n");
225 rc
= aspeed_lpc_snoop_config_irq(lpc_snoop
, pdev
);
229 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, dev
, 0, port
);
233 /* Configuration of 2nd snoop channel port is optional */
234 if (of_property_read_u32_index(dev
->of_node
, "snoop-ports",
236 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, dev
, 1, port
);
238 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
244 static int aspeed_lpc_snoop_remove(struct platform_device
*pdev
)
246 struct aspeed_lpc_snoop
*lpc_snoop
= dev_get_drvdata(&pdev
->dev
);
248 /* Disable both snoop channels */
249 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
250 aspeed_lpc_disable_snoop(lpc_snoop
, 1);
255 static const struct aspeed_lpc_snoop_model_data ast2400_model_data
= {
256 .has_hicrb_ensnp
= 0,
259 static const struct aspeed_lpc_snoop_model_data ast2500_model_data
= {
260 .has_hicrb_ensnp
= 1,
263 static const struct of_device_id aspeed_lpc_snoop_match
[] = {
264 { .compatible
= "aspeed,ast2400-lpc-snoop",
265 .data
= &ast2400_model_data
},
266 { .compatible
= "aspeed,ast2500-lpc-snoop",
267 .data
= &ast2500_model_data
},
271 static struct platform_driver aspeed_lpc_snoop_driver
= {
274 .of_match_table
= aspeed_lpc_snoop_match
,
276 .probe
= aspeed_lpc_snoop_probe
,
277 .remove
= aspeed_lpc_snoop_remove
,
280 module_platform_driver(aspeed_lpc_snoop_driver
);
282 MODULE_DEVICE_TABLE(of
, aspeed_lpc_snoop_match
);
283 MODULE_LICENSE("GPL");
284 MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
285 MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");