[PATCH 4/6] gpio: cbc-presence: Add CBC presence detect as GPIO driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




From: Georgi Vlaev <gvlaev@xxxxxxxxxxx>

This driver exports the CB FPGA presence detect bits from a
single 32bit CB register as GPIOs.

Signed-off-by: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
Signed-off-by: Guenter Roeck <groeck@xxxxxxxxxxx>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@xxxxxxxxxxxx>
---
 drivers/gpio/Kconfig             |  12 +
 drivers/gpio/Makefile            |   1 +
 drivers/gpio/gpio-cbc-presence.c | 460 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 473 insertions(+)
 create mode 100644 drivers/gpio/gpio-cbc-presence.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index b29f521..ef8f408 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -158,6 +158,18 @@ config GPIO_CBC
 	  This driver can also be built as a module.  If so, the module
 	  will be called gpio-cbc.
 
+config GPIO_CBC_PRESENCE
+	tristate "Juniper Networks PTX1K CBC presence detect as GPIO"
+	depends on MFD_JUNIPER_CBC && OF
+	default y if JNX_CONNECTOR
+	help
+	  This driver exports the CH_PRS and OTHER_CH_PRS presence detect
+	  bits of the PTX1K RCB CBC FPGA as GPIOs on the relevant Juniper
+	  platforms. Select if JNX_CONNECTOR is selected.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called gpio-cbc-presence.
+
 config GPIO_CLPS711X
 	tristate "CLPS711X GPIO support"
 	depends on ARCH_CLPS711X || COMPILE_TEST
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 78dd0d4..825c2636 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_GPIO_BCM_KONA)	+= gpio-bcm-kona.o
 obj-$(CONFIG_GPIO_BRCMSTB)	+= gpio-brcmstb.o
 obj-$(CONFIG_GPIO_BT8XX)	+= gpio-bt8xx.o
 obj-$(CONFIG_GPIO_CBC)		+= gpio-cbc.o
+obj-$(CONFIG_GPIO_CBC_PRESENCE)	+= gpio-cbc-presence.o
 obj-$(CONFIG_GPIO_CLPS711X)	+= gpio-clps711x.o
 obj-$(CONFIG_GPIO_CS5535)	+= gpio-cs5535.o
 obj-$(CONFIG_GPIO_CRYSTAL_COVE)	+= gpio-crystalcove.o
diff --git a/drivers/gpio/gpio-cbc-presence.c b/drivers/gpio/gpio-cbc-presence.c
new file mode 100644
index 0000000..ab16c0b
--- /dev/null
+++ b/drivers/gpio/gpio-cbc-presence.c
@@ -0,0 +1,460 @@
+/*
+ * Juniper Networks PTX1K CBC presence detect as GPIO driver
+ *
+ * Copyright (c) 2014, Juniper Networks
+ * Author: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/cbc-core.h>
+
+/*
+ * PTX1K RCB CBC:
+ * We have 26 presence bits in 2 regs.
+ * Interrupts are raised per bit change in a reg (2 ints)
+ *
+ * CBC_TOP_REGS_CH_PRS:
+ * FPC[7:0] -> FPC[7:0]
+ * FAN[16] -> FAN[0]
+ * CB[22:21] -> CB[1:0]
+ * FPD[23]
+ * OTHER_RE[26]
+ *
+ * CBC_TOP_REGS_OTHER_CH_PRS:
+ * PSM[4:0]
+ * SIB[10:5] -> SIB[5:0]
+ * SFPP[21:20] -> SFPP[1:0]
+ */
+
+/*
+ * struct cbc_presence_gpio - GPIO private data structure.
+ * @base: PCI base address of Memory mapped I/O register.
+ * @dev: Pointer to device structure.
+ * @gpio: Data for GPIO infrastructure.
+ */
+struct cbc_presence_gpio {
+	void __iomem *base;
+	struct device *dev;
+	struct gpio_chip gpio;
+	struct mutex irq_lock;
+	struct mutex work_lock;
+	struct irq_domain *domain;
+	int irq;
+	u32 reg;
+	unsigned long presence_cache;
+	unsigned long presence_irq_enabled;
+	unsigned long mask;
+	unsigned long always_present;
+	unsigned long poll_interval;
+	u8 irq_type[32];
+	struct delayed_work work;
+};
+
+static u32 cbc_presence_read(struct cbc_presence_gpio *cpg)
+{
+	return ioread32(cpg->base + cpg->reg) | cpg->always_present;
+}
+
+static int cbc_presence_gpio_get(struct gpio_chip *gc, unsigned int nr)
+{
+	struct cbc_presence_gpio *cpg =
+			container_of(gc, struct cbc_presence_gpio, gpio);
+	unsigned long data, pos, ord = 0;
+
+	data = cbc_presence_read(cpg);
+	for_each_set_bit(pos, &cpg->mask, fls(cpg->mask)) {
+		if (ord == nr)
+			return !!test_bit(ord, &data);
+		ord++;
+	}
+	return 0;
+}
+
+static int cbc_presence_gpio_direction_input(struct gpio_chip *gc,
+					     unsigned int nr)
+{
+	/* all pins are input pins */
+	return 0;
+}
+
+static int cbc_presence_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
+{
+	struct cbc_presence_gpio *cpg = container_of(gc,
+						struct cbc_presence_gpio, gpio);
+
+	return irq_create_mapping(cpg->domain, offset);
+}
+
+static void cbc_presence_irq_mask(struct irq_data *data)
+{
+	struct cbc_presence_gpio *cpg = irq_data_get_irq_chip_data(data);
+
+	clear_bit(data->hwirq, &cpg->presence_irq_enabled);
+}
+
+static void cbc_presence_irq_unmask(struct irq_data *data)
+{
+	struct cbc_presence_gpio *cpg = irq_data_get_irq_chip_data(data);
+
+	set_bit(data->hwirq, &cpg->presence_irq_enabled);
+}
+
+static int cbc_presence_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	struct cbc_presence_gpio *cpg = irq_data_get_irq_chip_data(data);
+
+	cpg->irq_type[data->hwirq] = type & 0x0f;
+
+	return 0;
+}
+
+static void cbc_presence_irq_bus_lock(struct irq_data *data)
+{
+	struct cbc_presence_gpio *cpg = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&cpg->irq_lock);
+}
+
+static void cbc_presence_irq_bus_unlock(struct irq_data *data)
+{
+	struct cbc_presence_gpio *cpg = irq_data_get_irq_chip_data(data);
+
+	/* Synchronize interrupts to chip */
+
+	mutex_unlock(&cpg->irq_lock);
+}
+
+static struct irq_chip cbc_presence_irq_chip = {
+	.name = "gpio-cbc-presence",
+	.irq_mask = cbc_presence_irq_mask,
+	.irq_unmask = cbc_presence_irq_unmask,
+	.irq_set_type = cbc_presence_irq_set_type,
+	.irq_bus_lock = cbc_presence_irq_bus_lock,
+	.irq_bus_sync_unlock = cbc_presence_irq_bus_unlock,
+};
+
+static int cbc_presence_gpio_irq_map(struct irq_domain *domain,
+				unsigned int irq, irq_hw_number_t hwirq)
+{
+	irq_set_chip_data(irq, domain->host_data);
+	irq_set_chip(irq, &cbc_presence_irq_chip);
+	irq_set_nested_thread(irq, true);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops cbc_presence_gpio_irq_domain_ops = {
+	.map = cbc_presence_gpio_irq_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+static void cbc_presence_gpio_irq_work(struct cbc_presence_gpio *cpg)
+{
+	unsigned long data, pos, ord = 0;
+
+	data = cbc_presence_read(cpg);
+
+	mutex_lock(&cpg->work_lock);
+	for_each_set_bit(pos, &cpg->mask, fls(cpg->mask)) {
+		int type, bit;
+
+		if (!test_bit(ord, &cpg->presence_irq_enabled)) {
+			ord++;
+			continue;
+		}
+
+		bit = test_bit(pos, &data);
+		if (bit == test_bit(pos, &cpg->presence_cache)) {
+			ord++;
+			continue;
+		}
+
+		type = cpg->irq_type[ord];
+		/*
+		 * check irq->type for match. Only handle edge triggered
+		 * interrupts; anything else doesn't make sense here.
+		 */
+		if (((type & IRQ_TYPE_EDGE_RISING) && bit) ||
+		    ((type & IRQ_TYPE_EDGE_FALLING) && !bit)) {
+			int virq = irq_find_mapping(cpg->domain, ord);
+
+			handle_nested_irq(virq);
+		}
+		ord++;
+	}
+	cpg->presence_cache = data;
+	mutex_unlock(&cpg->work_lock);
+}
+
+static irqreturn_t cbc_presence_gpio_irq_handler(int irq, void *data)
+{
+	struct cbc_presence_gpio *cpg = data;
+
+	cbc_presence_gpio_irq_work(cpg);
+
+	return IRQ_HANDLED;
+}
+
+static void cbc_presence_gpio_worker(struct work_struct *work)
+{
+	struct cbc_presence_gpio *cpg =
+		container_of(work, struct cbc_presence_gpio, work.work);
+
+	cbc_presence_gpio_irq_work(cpg);
+	schedule_delayed_work(&cpg->work,
+				msecs_to_jiffies(cpg->poll_interval));
+}
+
+static int cbc_presence_gpio_irq_setup(struct device *dev,
+					struct cbc_presence_gpio *cpg)
+{
+	int ret;
+
+	cpg->domain = irq_domain_add_linear(dev->of_node,
+				hweight_long(cpg->mask),
+				&cbc_presence_gpio_irq_domain_ops,
+				cpg);
+
+	if (cpg->domain == NULL)
+		return -ENOMEM;
+
+	INIT_DELAYED_WORK(&cpg->work, cbc_presence_gpio_worker);
+
+	if (cpg->irq) {
+		dev_info(dev, "Setting up interrupt %d\n", cpg->irq);
+		ret = devm_request_threaded_irq(dev, cpg->irq, NULL,
+						cbc_presence_gpio_irq_handler,
+						IRQF_ONESHOT,
+						dev_name(dev), cpg);
+		if (ret)
+			goto out_remove_domain;
+	} else {
+		schedule_delayed_work(&cpg->work, 1);
+	}
+
+	cpg->gpio.to_irq = cbc_presence_gpio_to_irq;
+
+	return 0;
+
+out_remove_domain:
+	irq_domain_remove(cpg->domain);
+	return ret;
+}
+
+static void cbc_presence_gpio_irq_teardown(struct device *dev,
+					struct cbc_presence_gpio *cpg)
+{
+	int i;
+
+	if (!cpg->irq)
+		cancel_delayed_work_sync(&cpg->work);
+
+	for (i = 0; i < cpg->gpio.ngpio; i++) {
+		int irq = irq_find_mapping(cpg->domain, i);
+
+		if (irq > 0)
+			irq_dispose_mapping(irq);
+	}
+	irq_domain_remove(cpg->domain);
+}
+
+static int cbc_presence_gpio_of_xlate(struct gpio_chip *gpio,
+				  const struct of_phandle_args *gpiospec,
+				  u32 *flags)
+{
+	if (WARN_ON(gpio->of_gpio_n_cells < 2))
+		return -EINVAL;
+
+	if (WARN_ON(gpiospec->args_count < gpio->of_gpio_n_cells))
+		return -EINVAL;
+
+	if (gpiospec->args[0] > gpio->ngpio)
+		return -EINVAL;
+
+	if (flags)
+		*flags = gpiospec->args[1] >> 16;
+
+	return gpiospec->args[0];
+}
+
+static void cbc_presence_gpio_setup(struct cbc_presence_gpio *cpg)
+{
+	struct gpio_chip *gpio = &cpg->gpio;
+
+	gpio->label = dev_name(cpg->dev);
+	gpio->owner = THIS_MODULE;
+	gpio->get = cbc_presence_gpio_get;
+	gpio->direction_input = cbc_presence_gpio_direction_input;
+	gpio->dbg_show = NULL;
+	gpio->base = -1;
+	gpio->ngpio = hweight_long(cpg->mask);
+	gpio->can_sleep = 0;
+	gpio->of_node = cpg->dev->of_node;
+	gpio->of_xlate = cbc_presence_gpio_of_xlate;
+	gpio->of_gpio_n_cells = 2;
+}
+
+static int cbc_presence_of_setup(struct cbc_presence_gpio *cpg)
+{
+	struct device *dev = cpg->dev;
+	struct device_node *node = dev->of_node;
+	u32 value;
+	int ret;
+
+	/* reg := CBC_TOP_REGS_CH_PRS | CBC_TOP_REGS_OTHER_CH_PRS */
+	ret = of_property_read_u32(node, "reg", &value);
+	if (ret) {
+		dev_err(dev, "failed to read the <reg> property.\n");
+		return ret;
+	}
+	cpg->reg = value;
+
+	/* mask := valid presence bits in <reg> */
+	ret = of_property_read_u32(node, "mask", &value);
+	if (ret)
+		value = 0xffffffff;
+	cpg->mask = value;
+
+	/*
+	 * always-present := some frus are always present, but not reported
+	 * e.g MP/BP on PTX1K & PTX3K
+	 */
+	ret = of_property_read_u32(node, "always-present", &value);
+	if (ret)
+		value = 0;
+	cpg->always_present = value;
+
+	/*
+	 * poll-interval := some CBC releses don't support interrupts.
+	 * Default poll interval is 1000 msec
+	 */
+	ret = of_property_read_u32(node, "poll-interval", &value);
+	if (ret)
+		value = 1000;
+	cpg->poll_interval = value;
+
+	return 0;
+}
+
+static int cbc_presence_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cbc_presence_gpio *cpg;
+	struct resource *res;
+	int ret;
+
+	cpg = devm_kzalloc(dev, sizeof(struct cbc_presence_gpio),
+			GFP_KERNEL);
+	if (cpg == NULL)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	cpg->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (IS_ERR(cpg->base))
+		return -ENOMEM;
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret > 0)
+		cpg->irq = ret;
+
+	cpg->dev = dev;
+	mutex_init(&cpg->irq_lock);
+	mutex_init(&cpg->work_lock);
+
+	ret = cbc_presence_of_setup(cpg);
+	if (ret)
+		return -ENODEV;
+
+	cbc_presence_gpio_setup(cpg);
+
+	/* cache the current presence */
+	cpg->presence_cache = cbc_presence_read(cpg);
+
+	ret = cbc_presence_gpio_irq_setup(dev, cpg);
+	if (ret < 0) {
+		dev_err(dev, "Failed to setup CBC presence irqs\n");
+		return ret;
+	}
+
+	ret = gpiochip_add(&cpg->gpio);
+	if (ret) {
+		dev_err(dev, "Failed to register CBC presence gpio\n");
+		goto err;
+	}
+
+	platform_set_drvdata(pdev, cpg);
+
+	return 0;
+err:
+	gpiochip_remove(&cpg->gpio);
+
+	if (cpg->domain)
+		cbc_presence_gpio_irq_teardown(dev, cpg);
+
+	return ret;
+}
+
+static int cbc_presence_gpio_remove(struct platform_device *pdev)
+{
+	struct cbc_presence_gpio *cpg = platform_get_drvdata(pdev);
+
+	cancel_delayed_work_sync(&cpg->work);
+	if (cpg->domain)
+		cbc_presence_gpio_irq_teardown(&pdev->dev, cpg);
+
+	gpiochip_remove(&cpg->gpio);
+
+	return 0;
+}
+
+static const struct of_device_id cbc_presence_gpio_ids[] = {
+	{ .compatible = "jnx,gpio-cbc-presence", },
+	/*
+	 * These are the same devices ... MFD OF hackery to
+	 * get around the single of_node compatible match
+	 * mfd_add_device() for the OTHER_CH_PRESENCE
+	 */
+	{ .compatible = "jnx,gpio-cbc-presence-other", },
+	{ .compatible = "jnx,gpio-cbc-presence-sib", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, cbc_presence_gpio_ids);
+
+static struct platform_driver cbc_presence_gpio_driver = {
+	.driver = {
+		.name = "gpio-cbc-presence",
+		.owner  = THIS_MODULE,
+		.of_match_table = cbc_presence_gpio_ids,
+	},
+	.probe = cbc_presence_gpio_probe,
+	.remove = cbc_presence_gpio_remove,
+};
+module_platform_driver(cbc_presence_gpio_driver);
+
+MODULE_DESCRIPTION("Juniper Networks CB presence detect as GPIO driver");
+MODULE_AUTHOR("Georgi Vlaev <gvlaev@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux