From patchwork Thu Nov 1 17:58:41 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pawel Moll X-Patchwork-Id: 1685771 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by patchwork1.kernel.org (Postfix) with ESMTP id 511363FCDF for ; Thu, 1 Nov 2012 18:01:35 +0000 (UTC) Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TTz3B-0002Ui-EF; Thu, 01 Nov 2012 17:59:30 +0000 Received: from service87.mimecast.com ([91.220.42.44]) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TTz31-0002St-HK for linux-arm-kernel@lists.infradead.org; Thu, 01 Nov 2012 17:59:21 +0000 Received: from cam-owa2.Emea.Arm.com (fw-tnat.cambridge.arm.com [217.140.96.21]) by service87.mimecast.com; Thu, 01 Nov 2012 17:59:15 +0000 Received: from hornet.cambridge.arm.com ([10.1.255.212]) by cam-owa2.Emea.Arm.com with Microsoft SMTPSVC(6.0.3790.3959); Thu, 1 Nov 2012 17:59:14 +0000 From: Pawel Moll To: Bryan Wu , Richard Purdie Subject: [PATCH 1/2] leds: Add generic support for memory mapped LEDs Date: Thu, 1 Nov 2012 17:58:41 +0000 Message-Id: <1351792722-15250-1-git-send-email-pawel.moll@arm.com> X-Mailer: git-send-email 1.7.10.4 X-OriginalArrivalTime: 01 Nov 2012 17:59:14.0187 (UTC) FILETIME=[9A7A0DB0:01CDB85A] X-MC-Unique: 112110117591508201 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20121101_135920_105287_DC0406C3 X-CRM114-Status: GOOD ( 15.38 ) X-Spam-Score: -2.6 (--) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-2.6 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.7 RCVD_IN_DNSWL_LOW RBL: Sender listed at http://www.dnswl.org/, low trust [91.220.42.44 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: linux-arm-kernel@lists.infradead.org, Pawel Moll , linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org LEDs are often controlled by writing to memory mapped register. This patch adds: 1. Generic functions for platform code and drivers to create class device for LEDs controlled by arbitrary bit masks. The control register value is read, modified by logic AND and OR operations with respective mask and written back. 2. A platform driver for simple use case when one or more LED are controlled by consecutive bits in a register pointed at by the platform device's memory resource. It can be particularly useful for MFD cells being part of an other device. Signed-off-by: Pawel Moll --- drivers/leds/Kconfig | 8 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-mmio.c | 250 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/leds.h | 38 +++++++ 4 files changed, 297 insertions(+) create mode 100644 drivers/leds/leds-mmio.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f508def..93707e6 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -457,6 +457,14 @@ config LEDS_BLINKM This option enables support for the BlinkM RGB LED connected through I2C. Say Y to enable support for the BlinkM LED. +config LEDS_MMIO + tristate "Generic LED support for memory mapped peripherals" + depends on LEDS_CLASS + depends on HAS_IOMEM + help + This option enables generic support for LEDs controlled via + memory mapped registers. + config LEDS_TRIGGERS bool "LED Trigger support" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 3fb9641..8e5d0c8 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o +obj-$(CONFIG_LEDS_MMIO) += leds-mmio.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/leds-mmio.c b/drivers/leds/leds-mmio.c new file mode 100644 index 0000000..1ef0cda --- /dev/null +++ b/drivers/leds/leds-mmio.c @@ -0,0 +1,250 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * Copyright (C) 2012 ARM Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +static u32 mmio_led_read(void __iomem *reg, unsigned reg_size) +{ + switch (reg_size) { + case 32: + return readl(reg); + case 16: + return readw(reg); + case 8: + return readb(reg); + } + return 0; +} + +static void mmio_led_write(void __iomem *reg, unsigned reg_size, u32 val) +{ + switch (reg_size) { + case 32: + writel(val, reg); + return; + case 16: + writew(val, reg); + return; + case 8: + writeb(val, reg); + return; + } +} + + +struct mmio_led { + struct led_classdev cdev; + spinlock_t *lock; + void __iomem *reg; + unsigned reg_size; + u32 off_and_mask, off_or_mask; + u32 on_and_mask, on_or_mask; +}; + +static void mmio_led_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct mmio_led *led = container_of(cdev, struct mmio_led, cdev); + unsigned long uninitialized_var(flags); + u32 val; + + if (led->lock) + spin_lock_irqsave(led->lock, flags); + + val = mmio_led_read(led->reg, led->reg_size); + if (brightness == LED_OFF) { + val &= led->off_and_mask; + val |= led->off_or_mask; + } else { + val &= led->on_and_mask; + val |= led->on_or_mask; + } + mmio_led_write(led->reg, led->reg_size, val); + + if (led->lock) + spin_unlock_irqrestore(led->lock, flags); +}; + +static enum led_brightness mmio_led_brightness_get(struct led_classdev *cdev) +{ + struct mmio_led *led = container_of(cdev, struct mmio_led, cdev); + unsigned long uninitialized_var(flags); + u32 val; + + if (led->lock) + spin_lock_irqsave(led->lock, flags); + val = mmio_led_read(led->reg, led->reg_size); + if (led->lock) + spin_unlock_irqrestore(led->lock, flags); + + if (((val & led->on_and_mask) | led->on_or_mask) == val) + return LED_FULL; + else + return LED_OFF; +} + +struct mmio_led *mmio_led_register(struct device *parent, spinlock_t *lock, + const char *name, const char *default_trigger, + void __iomem *reg, unsigned reg_size, u32 off_and_mask, + u32 off_or_mask, u32 on_and_mask, u32 on_or_mask) +{ + struct mmio_led *led; + int err; + + if (WARN_ON(reg_size != 8 && reg_size != 16 && reg_size != 32)) + return ERR_PTR(-EINVAL); + + led = kzalloc(sizeof(*led), GFP_KERNEL); + if (!led) + return ERR_PTR(-ENOMEM); + + led->cdev.brightness_set = mmio_led_brightness_set; + led->cdev.brightness_get = mmio_led_brightness_get; + led->cdev.name = name; + led->cdev.default_trigger = default_trigger; + + led->lock = lock; + led->reg = reg; + led->reg_size = reg_size; + led->off_and_mask = off_and_mask; + led->off_or_mask = off_or_mask; + led->on_and_mask = on_and_mask; + led->on_or_mask = on_or_mask; + + err = led_classdev_register(parent, &led->cdev); + if (err) { + kfree(led); + return ERR_PTR(err); + } + + return led; +} +EXPORT_SYMBOL_GPL(mmio_led_register); + +void mmio_led_unregister(struct mmio_led *led) +{ + led_classdev_unregister(&led->cdev); + kfree(led); +} +EXPORT_SYMBOL_GPL(mmio_led_unregister); + + +struct mmio_simple_leds { + spinlock_t lock; + int num_leds; + struct mmio_led *leds[0]; +}; + +static int __devinit mmio_simple_leds_probe(struct platform_device *pdev) +{ + struct mmio_simple_leds_platform_data *pdata = pdev->dev.platform_data; + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct mmio_simple_leds *leds; + void __iomem *reg; + u32 val, mask; + int i; + + if (!pdata) + return -EINVAL; + + if (pdata->reg_size != 8 && pdata->reg_size != 16 && + pdata->reg_size != 32) + return -EFAULT; + + leds = devm_kzalloc(&pdev->dev, sizeof(*leds) + + sizeof(leds->leds) * pdata->width, GFP_KERNEL); + if (!leds) + return -ENOMEM; + spin_lock_init(&leds->lock); + + if ((!pdev->mfd_cell || !pdev->mfd_cell->ignore_resource_conflicts) && + !devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name)) + return -EBUSY; + + reg = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!reg) + return -ENOMEM; + + val = mmio_led_read(reg, pdata->reg_size); + mask = ((1 << pdata->width) - 1) << pdata->shift; + if ((pdata->init_full && pdata->active_low) || + (pdata->init_off && !pdata->active_low)) + val &= ~mask; + else if ((pdata->init_off && pdata->active_low) || + (pdata->init_full && !pdata->active_low)) + val |= mask; + mmio_led_write(reg, pdata->reg_size, val); + + leds->num_leds = pdata->width; + for (i = 0; i < leds->num_leds; i++) { + unsigned shift = pdata->shift + i; + u32 and_mask = ~BIT(shift); + u32 off_or_mask = pdata->active_low ? BIT(shift) : 0; + u32 on_or_mask = pdata->active_low ? 0 : BIT(shift); + struct mmio_led *led = mmio_led_register(&pdev->dev, + &leds->lock, pdata->names[i], + pdata->default_triggers ? + pdata->default_triggers[i] : NULL, + reg, pdata->reg_size, and_mask, off_or_mask, + and_mask, on_or_mask); + + if (IS_ERR(led)) { + while (--i >= 0) + mmio_led_unregister(leds->leds[i]); + return PTR_ERR(led); + } + leds->leds[i] = led; + } + + platform_set_drvdata(pdev, leds); + + return 0; +} + +static int __devexit mmio_simple_leds_remove(struct platform_device *pdev) +{ + struct mmio_simple_leds *leds = platform_get_drvdata(pdev); + int i; + + platform_set_drvdata(pdev, NULL); + + for (i = 0; i < leds->num_leds; i++) + mmio_led_unregister(leds->leds[i]); + + return 0; +} + +static struct platform_driver mmio_simple_leds_driver = { + .probe = mmio_simple_leds_probe, + .remove = __devexit_p(mmio_simple_leds_remove), + .driver = { + .name = "leds-mmio-simple", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(mmio_simple_leds_driver); + +MODULE_AUTHOR("Pawel Moll "); +MODULE_DESCRIPTION("MMIO LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:leds-mmio-simple"); diff --git a/include/linux/leds.h b/include/linux/leds.h index 6e53bb3..a6338b0 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -241,6 +241,44 @@ struct gpio_led_platform_data { struct platform_device *gpio_led_register_device( int id, const struct gpio_led_platform_data *pdata); +/* For the leds-mmio driver */ + +struct mmio_led; + +#if defined(CONFIG_LEDS_MMIO) +/* Returns ERR_PTR in case of error */ +struct mmio_led *mmio_led_register(struct device *parent, spinlock_t *lock, + const char *name, const char *default_trigger, + void __iomem *reg, unsigned reg_size, u32 off_and_mask, + u32 off_or_mask, u32 on_and_mask, u32 on_or_mask); +void mmio_led_unregister(struct mmio_led *led); +#else +struct mmio_led *mmio_led_register(struct device *parent, spinlock_t *lock, + const char *name, const char *default_trigger, + void __iomem *reg, unsigned reg_size, u32 off_and_mask, + u32 off_or_mask, u32 on_and_mask, u32 on_or_mask) +{ + return NULL; +} + +void mmio_led_unregister(struct mmio_led *led) +{ +} +#endif + +struct mmio_simple_leds_platform_data { + unsigned reg_size; /* Register size (8/16/32) */ + unsigned shift; /* First bit controlling LEDs */ + unsigned width; /* Number of consecutive bits */ + const char **names; /* Must define 'width' names */ + const char **default_triggers; /* NULL or 'width' strings */ + bool active_low; + bool init_full; + bool init_off; +}; + +/* CPU led trigger */ + enum cpu_led_event { CPU_LED_IDLE_START, /* CPU enters idle */ CPU_LED_IDLE_END, /* CPU idle ends */