Linux Audio

Check our new training course

Loading...
Note: File does not exist in v6.13.7.
  1/*
  2 * Copyright 2017 Google Inc
  3 *
  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.
  8 *
  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.
 12 *
 13 * Typically used by the BMC to "watch" host boot progress via port
 14 * 0x80 writes made by the BIOS during the boot process.
 15 */
 16
 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>
 22#include <linux/of.h>
 23#include <linux/of_device.h>
 24#include <linux/platform_device.h>
 25#include <linux/regmap.h>
 26
 27#define DEVICE_NAME	"aspeed-lpc-snoop"
 28
 29#define NUM_SNOOP_CHANNELS 2
 30#define SNOOP_FIFO_SIZE 2048
 31
 32#define HICR5	0x0
 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)
 37
 38#define HICR6	0x4
 39#define HICR6_STR_SNP0W		BIT(0)
 40#define HICR6_STR_SNP1W		BIT(1)
 41#define SNPWADR	0x10
 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
 46#define SNPWDR	0x14
 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
 51#define HICRB	0x80
 52#define HICRB_ENSNP0D		BIT(14)
 53#define HICRB_ENSNP1D		BIT(15)
 54
 55struct aspeed_lpc_snoop_model_data {
 56	/* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500
 57	 * can use them.
 58	 */
 59	unsigned int has_hicrb_ensnp;
 60};
 61
 62struct aspeed_lpc_snoop {
 63	struct regmap		*regmap;
 64	int			irq;
 65	struct kfifo		snoop_fifo[NUM_SNOOP_CHANNELS];
 66};
 67
 68/* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
 69static void put_fifo_with_discard(struct kfifo *fifo, u8 val)
 70{
 71	if (!kfifo_initialized(fifo))
 72		return;
 73	if (kfifo_is_full(fifo))
 74		kfifo_skip(fifo);
 75	kfifo_put(fifo, val);
 76}
 77
 78static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg)
 79{
 80	struct aspeed_lpc_snoop *lpc_snoop = arg;
 81	u32 reg, data;
 82
 83	if (regmap_read(lpc_snoop->regmap, HICR6, &reg))
 84		return IRQ_NONE;
 85
 86	/* Check if one of the snoop channels is interrupting */
 87	reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W);
 88	if (!reg)
 89		return IRQ_NONE;
 90
 91	/* Ack pending IRQs */
 92	regmap_write(lpc_snoop->regmap, HICR6, reg);
 93
 94	/* Read and save most recent snoop'ed data byte to FIFO */
 95	regmap_read(lpc_snoop->regmap, SNPWDR, &data);
 96
 97	if (reg & HICR6_STR_SNP0W) {
 98		u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT;
 99
100		put_fifo_with_discard(&lpc_snoop->snoop_fifo[0], val);
101	}
102	if (reg & HICR6_STR_SNP1W) {
103		u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT;
104
105		put_fifo_with_discard(&lpc_snoop->snoop_fifo[1], val);
106	}
107
108	return IRQ_HANDLED;
109}
110
111static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop,
112				       struct platform_device *pdev)
113{
114	struct device *dev = &pdev->dev;
115	int rc;
116
117	lpc_snoop->irq = platform_get_irq(pdev, 0);
118	if (!lpc_snoop->irq)
119		return -ENODEV;
120
121	rc = devm_request_irq(dev, lpc_snoop->irq,
122			      aspeed_lpc_snoop_irq, IRQF_SHARED,
123			      DEVICE_NAME, lpc_snoop);
124	if (rc < 0) {
125		dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq);
126		lpc_snoop->irq = 0;
127		return rc;
128	}
129
130	return 0;
131}
132
133static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
134				   struct device *dev,
135				   int channel, u16 lpc_port)
136{
137	int rc = 0;
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);
141
142	/* Create FIFO datastructure */
143	rc = kfifo_alloc(&lpc_snoop->snoop_fifo[channel],
144			 SNOOP_FIFO_SIZE, GFP_KERNEL);
145	if (rc)
146		return rc;
147
148	/* Enable LPC snoop channel at requested port */
149	switch (channel) {
150	case 0:
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;
155		break;
156	case 1:
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;
161		break;
162	default:
163		return -EINVAL;
164	}
165
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,
171				hicrb_en, hicrb_en);
172
173	return rc;
174}
175
176static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
177				     int channel)
178{
179	switch (channel) {
180	case 0:
181		regmap_update_bits(lpc_snoop->regmap, HICR5,
182				   HICR5_EN_SNP0W | HICR5_ENINT_SNP0W,
183				   0);
184		break;
185	case 1:
186		regmap_update_bits(lpc_snoop->regmap, HICR5,
187				   HICR5_EN_SNP1W | HICR5_ENINT_SNP1W,
188				   0);
189		break;
190	default:
191		return;
192	}
193
194	kfifo_free(&lpc_snoop->snoop_fifo[channel]);
195}
196
197static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
198{
199	struct aspeed_lpc_snoop *lpc_snoop;
200	struct device *dev;
201	u32 port;
202	int rc;
203
204	dev = &pdev->dev;
205
206	lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL);
207	if (!lpc_snoop)
208		return -ENOMEM;
209
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");
214		return -ENODEV;
215	}
216
217	dev_set_drvdata(&pdev->dev, lpc_snoop);
218
219	rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port);
220	if (rc) {
221		dev_err(dev, "no snoop ports configured\n");
222		return -ENODEV;
223	}
224
225	rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev);
226	if (rc)
227		return rc;
228
229	rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port);
230	if (rc)
231		return rc;
232
233	/* Configuration of 2nd snoop channel port is optional */
234	if (of_property_read_u32_index(dev->of_node, "snoop-ports",
235				       1, &port) == 0) {
236		rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port);
237		if (rc)
238			aspeed_lpc_disable_snoop(lpc_snoop, 0);
239	}
240
241	return rc;
242}
243
244static int aspeed_lpc_snoop_remove(struct platform_device *pdev)
245{
246	struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev);
247
248	/* Disable both snoop channels */
249	aspeed_lpc_disable_snoop(lpc_snoop, 0);
250	aspeed_lpc_disable_snoop(lpc_snoop, 1);
251
252	return 0;
253}
254
255static const struct aspeed_lpc_snoop_model_data ast2400_model_data = {
256	.has_hicrb_ensnp = 0,
257};
258
259static const struct aspeed_lpc_snoop_model_data ast2500_model_data = {
260	.has_hicrb_ensnp = 1,
261};
262
263static 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 },
268	{ },
269};
270
271static struct platform_driver aspeed_lpc_snoop_driver = {
272	.driver = {
273		.name		= DEVICE_NAME,
274		.of_match_table = aspeed_lpc_snoop_match,
275	},
276	.probe = aspeed_lpc_snoop_probe,
277	.remove = aspeed_lpc_snoop_remove,
278};
279
280module_platform_driver(aspeed_lpc_snoop_driver);
281
282MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match);
283MODULE_LICENSE("GPL");
284MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
285MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");