162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* Copyright (c) 2020, Broadcom */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/init.h>
562306a36Sopenharmony_ci#include <linux/types.h>
662306a36Sopenharmony_ci#include <linux/module.h>
762306a36Sopenharmony_ci#include <linux/platform_device.h>
862306a36Sopenharmony_ci#include <linux/interrupt.h>
962306a36Sopenharmony_ci#include <linux/io.h>
1062306a36Sopenharmony_ci#include <linux/device.h>
1162306a36Sopenharmony_ci#include <linux/of.h>
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/kdebug.h>
1462306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistruct out_pin {
1762306a36Sopenharmony_ci	u32 enable_mask;
1862306a36Sopenharmony_ci	u32 value_mask;
1962306a36Sopenharmony_ci	u32 changed_mask;
2062306a36Sopenharmony_ci	u32 clr_changed_mask;
2162306a36Sopenharmony_ci	struct gpio_desc *gpiod;
2262306a36Sopenharmony_ci	const char *name;
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistruct in_pin {
2662306a36Sopenharmony_ci	u32 enable_mask;
2762306a36Sopenharmony_ci	u32 value_mask;
2862306a36Sopenharmony_ci	struct gpio_desc *gpiod;
2962306a36Sopenharmony_ci	const char *name;
3062306a36Sopenharmony_ci	struct brcmstb_usb_pinmap_data *pdata;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistruct brcmstb_usb_pinmap_data {
3462306a36Sopenharmony_ci	void __iomem *regs;
3562306a36Sopenharmony_ci	int in_count;
3662306a36Sopenharmony_ci	struct in_pin *in_pins;
3762306a36Sopenharmony_ci	int out_count;
3862306a36Sopenharmony_ci	struct out_pin *out_pins;
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic void pinmap_set(void __iomem *reg, u32 mask)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	u32 val;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	val = readl(reg);
4762306a36Sopenharmony_ci	val |= mask;
4862306a36Sopenharmony_ci	writel(val, reg);
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic void pinmap_unset(void __iomem *reg, u32 mask)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	u32 val;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	val = readl(reg);
5662306a36Sopenharmony_ci	val &= ~mask;
5762306a36Sopenharmony_ci	writel(val, reg);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic void sync_in_pin(struct in_pin *pin)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	u32 val;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	val = gpiod_get_value(pin->gpiod);
6562306a36Sopenharmony_ci	if (val)
6662306a36Sopenharmony_ci		pinmap_set(pin->pdata->regs, pin->value_mask);
6762306a36Sopenharmony_ci	else
6862306a36Sopenharmony_ci		pinmap_unset(pin->pdata->regs, pin->value_mask);
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci/*
7262306a36Sopenharmony_ci * Interrupt from override register, propagate from override bit
7362306a36Sopenharmony_ci * to GPIO.
7462306a36Sopenharmony_ci */
7562306a36Sopenharmony_cistatic irqreturn_t brcmstb_usb_pinmap_ovr_isr(int irq, void *dev_id)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	struct brcmstb_usb_pinmap_data *pdata = dev_id;
7862306a36Sopenharmony_ci	struct out_pin *pout;
7962306a36Sopenharmony_ci	u32 val;
8062306a36Sopenharmony_ci	u32 bit;
8162306a36Sopenharmony_ci	int x;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	pr_debug("%s: reg: 0x%x\n", __func__, readl(pdata->regs));
8462306a36Sopenharmony_ci	pout = pdata->out_pins;
8562306a36Sopenharmony_ci	for (x = 0; x < pdata->out_count; x++) {
8662306a36Sopenharmony_ci		val = readl(pdata->regs);
8762306a36Sopenharmony_ci		if (val & pout->changed_mask) {
8862306a36Sopenharmony_ci			pinmap_set(pdata->regs, pout->clr_changed_mask);
8962306a36Sopenharmony_ci			pinmap_unset(pdata->regs, pout->clr_changed_mask);
9062306a36Sopenharmony_ci			bit = val & pout->value_mask;
9162306a36Sopenharmony_ci			gpiod_set_value(pout->gpiod, bit ? 1 : 0);
9262306a36Sopenharmony_ci			pr_debug("%s: %s bit changed state to %d\n",
9362306a36Sopenharmony_ci				 __func__, pout->name, bit ? 1 : 0);
9462306a36Sopenharmony_ci		}
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci	return IRQ_HANDLED;
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci/*
10062306a36Sopenharmony_ci * Interrupt from GPIO, propagate from GPIO to override bit.
10162306a36Sopenharmony_ci */
10262306a36Sopenharmony_cistatic irqreturn_t brcmstb_usb_pinmap_gpio_isr(int irq, void *dev_id)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	struct in_pin *pin = dev_id;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	pr_debug("%s: %s pin changed state\n", __func__, pin->name);
10762306a36Sopenharmony_ci	sync_in_pin(pin);
10862306a36Sopenharmony_ci	return IRQ_HANDLED;
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic void get_pin_counts(struct device_node *dn, int *in_count,
11362306a36Sopenharmony_ci			   int *out_count)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	int in;
11662306a36Sopenharmony_ci	int out;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	*in_count = 0;
11962306a36Sopenharmony_ci	*out_count = 0;
12062306a36Sopenharmony_ci	in = of_property_count_strings(dn, "brcm,in-functions");
12162306a36Sopenharmony_ci	if (in < 0)
12262306a36Sopenharmony_ci		return;
12362306a36Sopenharmony_ci	out = of_property_count_strings(dn, "brcm,out-functions");
12462306a36Sopenharmony_ci	if (out < 0)
12562306a36Sopenharmony_ci		return;
12662306a36Sopenharmony_ci	*in_count = in;
12762306a36Sopenharmony_ci	*out_count = out;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic int parse_pins(struct device *dev, struct device_node *dn,
13162306a36Sopenharmony_ci		      struct brcmstb_usb_pinmap_data *pdata)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	struct out_pin *pout;
13462306a36Sopenharmony_ci	struct in_pin *pin;
13562306a36Sopenharmony_ci	int index;
13662306a36Sopenharmony_ci	int res;
13762306a36Sopenharmony_ci	int x;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	pin = pdata->in_pins;
14062306a36Sopenharmony_ci	for (x = 0, index = 0; x < pdata->in_count; x++) {
14162306a36Sopenharmony_ci		pin->gpiod = devm_gpiod_get_index(dev, "in", x, GPIOD_IN);
14262306a36Sopenharmony_ci		if (IS_ERR(pin->gpiod)) {
14362306a36Sopenharmony_ci			dev_err(dev, "Error getting gpio %s\n", pin->name);
14462306a36Sopenharmony_ci			return PTR_ERR(pin->gpiod);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci		}
14762306a36Sopenharmony_ci		res = of_property_read_string_index(dn, "brcm,in-functions", x,
14862306a36Sopenharmony_ci						    &pin->name);
14962306a36Sopenharmony_ci		if (res < 0) {
15062306a36Sopenharmony_ci			dev_err(dev, "Error getting brcm,in-functions for %s\n",
15162306a36Sopenharmony_ci				pin->name);
15262306a36Sopenharmony_ci			return res;
15362306a36Sopenharmony_ci		}
15462306a36Sopenharmony_ci		res = of_property_read_u32_index(dn, "brcm,in-masks", index++,
15562306a36Sopenharmony_ci						 &pin->enable_mask);
15662306a36Sopenharmony_ci		if (res < 0) {
15762306a36Sopenharmony_ci			dev_err(dev, "Error getting 1st brcm,in-masks for %s\n",
15862306a36Sopenharmony_ci				pin->name);
15962306a36Sopenharmony_ci			return res;
16062306a36Sopenharmony_ci		}
16162306a36Sopenharmony_ci		res = of_property_read_u32_index(dn, "brcm,in-masks", index++,
16262306a36Sopenharmony_ci						 &pin->value_mask);
16362306a36Sopenharmony_ci		if (res < 0) {
16462306a36Sopenharmony_ci			dev_err(dev, "Error getting 2nd brcm,in-masks for %s\n",
16562306a36Sopenharmony_ci				pin->name);
16662306a36Sopenharmony_ci			return res;
16762306a36Sopenharmony_ci		}
16862306a36Sopenharmony_ci		pin->pdata = pdata;
16962306a36Sopenharmony_ci		pin++;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci	pout = pdata->out_pins;
17262306a36Sopenharmony_ci	for (x = 0, index = 0; x < pdata->out_count; x++) {
17362306a36Sopenharmony_ci		pout->gpiod = devm_gpiod_get_index(dev, "out", x,
17462306a36Sopenharmony_ci						   GPIOD_OUT_HIGH);
17562306a36Sopenharmony_ci		if (IS_ERR(pout->gpiod)) {
17662306a36Sopenharmony_ci			dev_err(dev, "Error getting gpio %s\n", pin->name);
17762306a36Sopenharmony_ci			return PTR_ERR(pout->gpiod);
17862306a36Sopenharmony_ci		}
17962306a36Sopenharmony_ci		res = of_property_read_string_index(dn, "brcm,out-functions", x,
18062306a36Sopenharmony_ci						    &pout->name);
18162306a36Sopenharmony_ci		if (res < 0) {
18262306a36Sopenharmony_ci			dev_err(dev, "Error getting brcm,out-functions for %s\n",
18362306a36Sopenharmony_ci				pout->name);
18462306a36Sopenharmony_ci			return res;
18562306a36Sopenharmony_ci		}
18662306a36Sopenharmony_ci		res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
18762306a36Sopenharmony_ci						 &pout->enable_mask);
18862306a36Sopenharmony_ci		if (res < 0) {
18962306a36Sopenharmony_ci			dev_err(dev, "Error getting 1st brcm,out-masks for %s\n",
19062306a36Sopenharmony_ci				pout->name);
19162306a36Sopenharmony_ci			return res;
19262306a36Sopenharmony_ci		}
19362306a36Sopenharmony_ci		res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
19462306a36Sopenharmony_ci						 &pout->value_mask);
19562306a36Sopenharmony_ci		if (res < 0) {
19662306a36Sopenharmony_ci			dev_err(dev, "Error getting 2nd brcm,out-masks for %s\n",
19762306a36Sopenharmony_ci				pout->name);
19862306a36Sopenharmony_ci			return res;
19962306a36Sopenharmony_ci		}
20062306a36Sopenharmony_ci		res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
20162306a36Sopenharmony_ci						 &pout->changed_mask);
20262306a36Sopenharmony_ci		if (res < 0) {
20362306a36Sopenharmony_ci			dev_err(dev, "Error getting 3rd brcm,out-masks for %s\n",
20462306a36Sopenharmony_ci				pout->name);
20562306a36Sopenharmony_ci			return res;
20662306a36Sopenharmony_ci		}
20762306a36Sopenharmony_ci		res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
20862306a36Sopenharmony_ci						 &pout->clr_changed_mask);
20962306a36Sopenharmony_ci		if (res < 0) {
21062306a36Sopenharmony_ci			dev_err(dev, "Error getting 4th out-masks for %s\n",
21162306a36Sopenharmony_ci				pout->name);
21262306a36Sopenharmony_ci			return res;
21362306a36Sopenharmony_ci		}
21462306a36Sopenharmony_ci		pout++;
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ci	return 0;
21762306a36Sopenharmony_ci}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_cistatic void sync_all_pins(struct brcmstb_usb_pinmap_data *pdata)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	struct out_pin *pout;
22262306a36Sopenharmony_ci	struct in_pin *pin;
22362306a36Sopenharmony_ci	int val;
22462306a36Sopenharmony_ci	int x;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	/*
22762306a36Sopenharmony_ci	 * Enable the override, clear any changed condition and
22862306a36Sopenharmony_ci	 * propagate the state to the GPIO for all out pins.
22962306a36Sopenharmony_ci	 */
23062306a36Sopenharmony_ci	pout = pdata->out_pins;
23162306a36Sopenharmony_ci	for (x = 0; x < pdata->out_count; x++) {
23262306a36Sopenharmony_ci		pinmap_set(pdata->regs, pout->enable_mask);
23362306a36Sopenharmony_ci		pinmap_set(pdata->regs, pout->clr_changed_mask);
23462306a36Sopenharmony_ci		pinmap_unset(pdata->regs, pout->clr_changed_mask);
23562306a36Sopenharmony_ci		val = readl(pdata->regs) & pout->value_mask;
23662306a36Sopenharmony_ci		gpiod_set_value(pout->gpiod, val ? 1 : 0);
23762306a36Sopenharmony_ci		pout++;
23862306a36Sopenharmony_ci	}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	/* sync and enable all in pins. */
24162306a36Sopenharmony_ci	pin = pdata->in_pins;
24262306a36Sopenharmony_ci	for (x = 0; x < pdata->in_count; x++) {
24362306a36Sopenharmony_ci		sync_in_pin(pin);
24462306a36Sopenharmony_ci		pinmap_set(pdata->regs, pin->enable_mask);
24562306a36Sopenharmony_ci		pin++;
24662306a36Sopenharmony_ci	}
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	struct device_node *dn = pdev->dev.of_node;
25262306a36Sopenharmony_ci	struct brcmstb_usb_pinmap_data *pdata;
25362306a36Sopenharmony_ci	struct in_pin *pin;
25462306a36Sopenharmony_ci	struct resource *r;
25562306a36Sopenharmony_ci	int out_count;
25662306a36Sopenharmony_ci	int in_count;
25762306a36Sopenharmony_ci	int err;
25862306a36Sopenharmony_ci	int irq;
25962306a36Sopenharmony_ci	int x;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	get_pin_counts(dn, &in_count, &out_count);
26262306a36Sopenharmony_ci	if ((in_count + out_count) == 0)
26362306a36Sopenharmony_ci		return -EINVAL;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
26662306a36Sopenharmony_ci	if (!r)
26762306a36Sopenharmony_ci		return -EINVAL;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	pdata = devm_kzalloc(&pdev->dev,
27062306a36Sopenharmony_ci			     sizeof(*pdata) +
27162306a36Sopenharmony_ci			     (sizeof(struct in_pin) * in_count) +
27262306a36Sopenharmony_ci			     (sizeof(struct out_pin) * out_count), GFP_KERNEL);
27362306a36Sopenharmony_ci	if (!pdata)
27462306a36Sopenharmony_ci		return -ENOMEM;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	pdata->in_count = in_count;
27762306a36Sopenharmony_ci	pdata->out_count = out_count;
27862306a36Sopenharmony_ci	pdata->in_pins = (struct in_pin *)(pdata + 1);
27962306a36Sopenharmony_ci	pdata->out_pins = (struct out_pin *)(pdata->in_pins + in_count);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	pdata->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
28262306a36Sopenharmony_ci	if (!pdata->regs)
28362306a36Sopenharmony_ci		return -ENOMEM;
28462306a36Sopenharmony_ci	platform_set_drvdata(pdev, pdata);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	err = parse_pins(&pdev->dev, dn, pdata);
28762306a36Sopenharmony_ci	if (err)
28862306a36Sopenharmony_ci		return err;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	sync_all_pins(pdata);
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	if (out_count) {
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci		/* Enable interrupt for out pins */
29562306a36Sopenharmony_ci		irq = platform_get_irq(pdev, 0);
29662306a36Sopenharmony_ci		if (irq < 0)
29762306a36Sopenharmony_ci			return irq;
29862306a36Sopenharmony_ci		err = devm_request_irq(&pdev->dev, irq,
29962306a36Sopenharmony_ci				       brcmstb_usb_pinmap_ovr_isr,
30062306a36Sopenharmony_ci				       IRQF_TRIGGER_RISING,
30162306a36Sopenharmony_ci				       pdev->name, pdata);
30262306a36Sopenharmony_ci		if (err < 0) {
30362306a36Sopenharmony_ci			dev_err(&pdev->dev, "Error requesting IRQ\n");
30462306a36Sopenharmony_ci			return err;
30562306a36Sopenharmony_ci		}
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	for (x = 0, pin = pdata->in_pins; x < pdata->in_count; x++, pin++) {
30962306a36Sopenharmony_ci		irq = gpiod_to_irq(pin->gpiod);
31062306a36Sopenharmony_ci		if (irq < 0) {
31162306a36Sopenharmony_ci			dev_err(&pdev->dev, "Error getting IRQ for %s pin\n",
31262306a36Sopenharmony_ci				pin->name);
31362306a36Sopenharmony_ci			return irq;
31462306a36Sopenharmony_ci		}
31562306a36Sopenharmony_ci		err = devm_request_irq(&pdev->dev, irq,
31662306a36Sopenharmony_ci				       brcmstb_usb_pinmap_gpio_isr,
31762306a36Sopenharmony_ci				       IRQF_SHARED | IRQF_TRIGGER_RISING |
31862306a36Sopenharmony_ci				       IRQF_TRIGGER_FALLING,
31962306a36Sopenharmony_ci				       pdev->name, pin);
32062306a36Sopenharmony_ci		if (err < 0) {
32162306a36Sopenharmony_ci			dev_err(&pdev->dev, "Error requesting IRQ for %s pin\n",
32262306a36Sopenharmony_ci				pin->name);
32362306a36Sopenharmony_ci			return err;
32462306a36Sopenharmony_ci		}
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	dev_dbg(&pdev->dev, "Driver probe succeeded\n");
32862306a36Sopenharmony_ci	dev_dbg(&pdev->dev, "In pin count: %d, out pin count: %d\n",
32962306a36Sopenharmony_ci		pdata->in_count, pdata->out_count);
33062306a36Sopenharmony_ci	return 0;
33162306a36Sopenharmony_ci}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_cistatic const struct of_device_id brcmstb_usb_pinmap_of_match[] = {
33562306a36Sopenharmony_ci	{ .compatible = "brcm,usb-pinmap" },
33662306a36Sopenharmony_ci	{ },
33762306a36Sopenharmony_ci};
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_cistatic struct platform_driver brcmstb_usb_pinmap_driver = {
34062306a36Sopenharmony_ci	.driver = {
34162306a36Sopenharmony_ci		.name	= "brcm-usb-pinmap",
34262306a36Sopenharmony_ci		.of_match_table = brcmstb_usb_pinmap_of_match,
34362306a36Sopenharmony_ci	},
34462306a36Sopenharmony_ci};
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_cistatic int __init brcmstb_usb_pinmap_init(void)
34762306a36Sopenharmony_ci{
34862306a36Sopenharmony_ci	return platform_driver_probe(&brcmstb_usb_pinmap_driver,
34962306a36Sopenharmony_ci				     brcmstb_usb_pinmap_probe);
35062306a36Sopenharmony_ci}
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_cimodule_init(brcmstb_usb_pinmap_init);
35362306a36Sopenharmony_ciMODULE_AUTHOR("Al Cooper <alcooperx@gmail.com>");
35462306a36Sopenharmony_ciMODULE_DESCRIPTION("Broadcom USB Pinmap Driver");
35562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
356