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/platform_device.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
{
55 struct regmap
*regmap
;
57 struct kfifo snoop_fifo
[NUM_SNOOP_CHANNELS
];
60 /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
61 static void put_fifo_with_discard(struct kfifo
*fifo
, u8 val
)
63 if (!kfifo_initialized(fifo
))
65 if (kfifo_is_full(fifo
))
70 static irqreturn_t
aspeed_lpc_snoop_irq(int irq
, void *arg
)
72 struct aspeed_lpc_snoop
*lpc_snoop
= arg
;
75 if (regmap_read(lpc_snoop
->regmap
, HICR6
, ®
))
78 /* Check if one of the snoop channels is interrupting */
79 reg
&= (HICR6_STR_SNP0W
| HICR6_STR_SNP1W
);
83 /* Ack pending IRQs */
84 regmap_write(lpc_snoop
->regmap
, HICR6
, reg
);
86 /* Read and save most recent snoop'ed data byte to FIFO */
87 regmap_read(lpc_snoop
->regmap
, SNPWDR
, &data
);
89 if (reg
& HICR6_STR_SNP0W
) {
90 u8 val
= (data
& SNPWDR_CH0_MASK
) >> SNPWDR_CH0_SHIFT
;
92 put_fifo_with_discard(&lpc_snoop
->snoop_fifo
[0], val
);
94 if (reg
& HICR6_STR_SNP1W
) {
95 u8 val
= (data
& SNPWDR_CH1_MASK
) >> SNPWDR_CH1_SHIFT
;
97 put_fifo_with_discard(&lpc_snoop
->snoop_fifo
[1], val
);
103 static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop
*lpc_snoop
,
104 struct platform_device
*pdev
)
106 struct device
*dev
= &pdev
->dev
;
109 lpc_snoop
->irq
= platform_get_irq(pdev
, 0);
113 rc
= devm_request_irq(dev
, lpc_snoop
->irq
,
114 aspeed_lpc_snoop_irq
, IRQF_SHARED
,
115 DEVICE_NAME
, lpc_snoop
);
117 dev_warn(dev
, "Unable to request IRQ %d\n", lpc_snoop
->irq
);
125 static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
126 int channel
, u16 lpc_port
)
129 u32 hicr5_en
, snpwadr_mask
, snpwadr_shift
, hicrb_en
;
131 /* Create FIFO datastructure */
132 rc
= kfifo_alloc(&lpc_snoop
->snoop_fifo
[channel
],
133 SNOOP_FIFO_SIZE
, GFP_KERNEL
);
137 /* Enable LPC snoop channel at requested port */
140 hicr5_en
= HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
;
141 snpwadr_mask
= SNPWADR_CH0_MASK
;
142 snpwadr_shift
= SNPWADR_CH0_SHIFT
;
143 hicrb_en
= HICRB_ENSNP0D
;
146 hicr5_en
= HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
;
147 snpwadr_mask
= SNPWADR_CH1_MASK
;
148 snpwadr_shift
= SNPWADR_CH1_SHIFT
;
149 hicrb_en
= HICRB_ENSNP1D
;
155 regmap_update_bits(lpc_snoop
->regmap
, HICR5
, hicr5_en
, hicr5_en
);
156 regmap_update_bits(lpc_snoop
->regmap
, SNPWADR
, snpwadr_mask
,
157 lpc_port
<< snpwadr_shift
);
158 regmap_update_bits(lpc_snoop
->regmap
, HICRB
, hicrb_en
, hicrb_en
);
163 static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop
*lpc_snoop
,
168 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
169 HICR5_EN_SNP0W
| HICR5_ENINT_SNP0W
,
173 regmap_update_bits(lpc_snoop
->regmap
, HICR5
,
174 HICR5_EN_SNP1W
| HICR5_ENINT_SNP1W
,
181 kfifo_free(&lpc_snoop
->snoop_fifo
[channel
]);
184 static int aspeed_lpc_snoop_probe(struct platform_device
*pdev
)
186 struct aspeed_lpc_snoop
*lpc_snoop
;
193 lpc_snoop
= devm_kzalloc(dev
, sizeof(*lpc_snoop
), GFP_KERNEL
);
197 lpc_snoop
->regmap
= syscon_node_to_regmap(
198 pdev
->dev
.parent
->of_node
);
199 if (IS_ERR(lpc_snoop
->regmap
)) {
200 dev_err(dev
, "Couldn't get regmap\n");
204 dev_set_drvdata(&pdev
->dev
, lpc_snoop
);
206 rc
= of_property_read_u32_index(dev
->of_node
, "snoop-ports", 0, &port
);
208 dev_err(dev
, "no snoop ports configured\n");
212 rc
= aspeed_lpc_snoop_config_irq(lpc_snoop
, pdev
);
216 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, 0, port
);
220 /* Configuration of 2nd snoop channel port is optional */
221 if (of_property_read_u32_index(dev
->of_node
, "snoop-ports",
223 rc
= aspeed_lpc_enable_snoop(lpc_snoop
, 1, port
);
225 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
231 static int aspeed_lpc_snoop_remove(struct platform_device
*pdev
)
233 struct aspeed_lpc_snoop
*lpc_snoop
= dev_get_drvdata(&pdev
->dev
);
235 /* Disable both snoop channels */
236 aspeed_lpc_disable_snoop(lpc_snoop
, 0);
237 aspeed_lpc_disable_snoop(lpc_snoop
, 1);
242 static const struct of_device_id aspeed_lpc_snoop_match
[] = {
243 { .compatible
= "aspeed,ast2500-lpc-snoop" },
247 static struct platform_driver aspeed_lpc_snoop_driver
= {
250 .of_match_table
= aspeed_lpc_snoop_match
,
252 .probe
= aspeed_lpc_snoop_probe
,
253 .remove
= aspeed_lpc_snoop_remove
,
256 module_platform_driver(aspeed_lpc_snoop_driver
);
258 MODULE_DEVICE_TABLE(of
, aspeed_lpc_snoop_match
);
259 MODULE_LICENSE("GPL");
260 MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
261 MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");