Commit 85724ede authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'leds_for_4.12' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds

Pull LED updates from Jacek Anaszewski:
 "New drivers:

   - add LED support for MT6323 PMIC

   - add LED support for Motorola CPCAP PMIC

  New features and improvements:

   - add LED trigger for all CPUs aggregated which is useful on tiny
     boards with more CPU cores than LED pins

   - add OF variants of LED registering functions as a preparation for
     adding generic support for Device Tree parsing

   - dell-led improvements and cleanups, followed by moving it to the
     x86 platform driver subsystem which is a more appropriate place for
     it

   - extend pca9532 Device Tree support by adding the LEDs
     'default-state' property

   - extend pca963x Device Tree support by adding nxp,inverted-out
     property for inverting the polarity of the output

   - remove ACPI support for lp3952 since it relied on a non-official
     ACPI IDs"

* tag 'leds_for_4.12' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds:
  leds: pca9532: Extend pca9532 device tree support
  leds: cpcap: new driver
  mfd: cpcap: Add missing include dependencies
  leds: lp3952: Use 'if (ret)' pattern
  leds: lp3952: Remove ACPI support for lp3952
  leds: mt6323: Fix an off by one bug in probe
  dt-bindings: leds: Add document bindings for leds-mt6323
  leds: Add LED support for MT6323 PMIC
  leds: gpio: use OF variant of LED registering function
  leds: core: add OF variants of LED registering functions
  platform/x86: dell-wmi-led: fix coding style issues
  dell-led: move driver to drivers/platform/x86/dell-wmi-led.c
  dell-led: remove code related to mic mute LED
  platform/x86: dell-laptop: import dell_micmute_led_set() from drivers/leds/dell-led.c
  ALSA: hda - rename dell_led_set_func to dell_micmute_led_set_func
  ALSA: hda - use dell_micmute_led_set() instead of dell_app_wmi_led_set()
  dell-led: remove GUID check from dell_micmute_led_set()
  leds/trigger/cpu: Add LED trigger for all CPUs aggregated
parents 477d7cae 28c5fe99
Motorola CPCAP PMIC LEDs
------------------------
This module is part of the CPCAP. For more details about the whole
chip see Documentation/devicetree/bindings/mfd/motorola-cpcap.txt.
Requires node properties:
- compatible: should be one of
* "motorola,cpcap-led-mdl" (Main Display Lighting)
* "motorola,cpcap-led-kl" (Keyboard Lighting)
* "motorola,cpcap-led-adl" (Aux Display Lighting)
* "motorola,cpcap-led-red" (Red Triode)
* "motorola,cpcap-led-green" (Green Triode)
* "motorola,cpcap-led-blue" (Blue Triode)
* "motorola,cpcap-led-cf" (Camera Flash)
* "motorola,cpcap-led-bt" (Bluetooth)
* "motorola,cpcap-led-cp" (Camera Privacy LED)
- label: see Documentation/devicetree/bindings/leds/common.txt
- vdd-supply: A phandle to the regulator powering the LED
Example:
&cpcap {
cpcap_led_red: red-led {
compatible = "motorola,cpcap-led-red";
label = "cpcap:red";
vdd-supply = <&sw5>;
};
};
Device Tree Bindings for LED support on MT6323 PMIC
MT6323 LED controller is subfunction provided by MT6323 PMIC, so the LED
controllers are defined as the subnode of the function node provided by MT6323
PMIC controller that is being defined as one kind of Muti-Function Device (MFD)
using shared bus called PMIC wrapper for each subfunction to access remote
MT6323 PMIC hardware.
For MT6323 MFD bindings see:
Documentation/devicetree/bindings/mfd/mt6397.txt
For MediaTek PMIC wrapper bindings see:
Documentation/devicetree/bindings/soc/mediatek/pwrap.txt
Required properties:
- compatible : Must be "mediatek,mt6323-led"
- address-cells : Must be 1
- size-cells : Must be 0
Each led is represented as a child node of the mediatek,mt6323-led that
describes the initial behavior for each LED physically and currently only four
LED child nodes can be supported.
Required properties for the LED child node:
- reg : LED channel number (0..3)
Optional properties for the LED child node:
- label : See Documentation/devicetree/bindings/leds/common.txt
- linux,default-trigger : See Documentation/devicetree/bindings/leds/common.txt
- default-state: See Documentation/devicetree/bindings/leds/common.txt
Example:
mt6323: pmic {
compatible = "mediatek,mt6323";
...
mt6323led: leds {
compatible = "mediatek,mt6323-led";
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
label = "LED0";
linux,default-trigger = "timer";
default-state = "on";
};
led@1 {
reg = <1>;
label = "LED1";
default-state = "off";
};
led@2 {
reg = <2>;
label = "LED2";
default-state = "on";
};
};
};
...@@ -17,6 +17,8 @@ Optional sub-node properties: ...@@ -17,6 +17,8 @@ Optional sub-node properties:
- label: see Documentation/devicetree/bindings/leds/common.txt - label: see Documentation/devicetree/bindings/leds/common.txt
- type: Output configuration, see dt-bindings/leds/leds-pca9532.h (default NONE) - type: Output configuration, see dt-bindings/leds/leds-pca9532.h (default NONE)
- linux,default-trigger: see Documentation/devicetree/bindings/leds/common.txt - linux,default-trigger: see Documentation/devicetree/bindings/leds/common.txt
- default-state: see Documentation/devicetree/bindings/leds/common.txt
This property is only valid for sub-nodes of type <PCA9532_TYPE_LED>.
Example: Example:
#include <dt-bindings/leds/leds-pca9532.h> #include <dt-bindings/leds/leds-pca9532.h>
...@@ -33,6 +35,14 @@ Example: ...@@ -33,6 +35,14 @@ Example:
label = "pca:green:power"; label = "pca:green:power";
type = <PCA9532_TYPE_LED>; type = <PCA9532_TYPE_LED>;
}; };
kernel-booting {
type = <PCA9532_TYPE_LED>;
default-state = "on";
};
sys-stat {
type = <PCA9532_TYPE_LED>;
default-state = "keep"; // don't touch, was set by U-Boot
};
}; };
For more product information please see the link below: For more product information please see the link below:
......
...@@ -76,6 +76,15 @@ config LEDS_BCM6358 ...@@ -76,6 +76,15 @@ config LEDS_BCM6358
This option enables support for LEDs connected to the BCM6358 This option enables support for LEDs connected to the BCM6358
LED HW controller accessed via MMIO registers. LED HW controller accessed via MMIO registers.
config LEDS_CPCAP
tristate "LED Support for Motorola CPCAP"
depends on LEDS_CLASS
depends on MFD_CPCAP
depends on OF
help
This option enables support for LEDs offered by Motorola's
CPCAP PMIC.
config LEDS_LM3530 config LEDS_LM3530
tristate "LCD Backlight driver for LM3530" tristate "LCD Backlight driver for LM3530"
depends on LEDS_CLASS depends on LEDS_CLASS
...@@ -126,6 +135,14 @@ config LEDS_MIKROTIK_RB532 ...@@ -126,6 +135,14 @@ config LEDS_MIKROTIK_RB532
This option enables support for the so called "User LED" of This option enables support for the so called "User LED" of
Mikrotik's Routerboard 532. Mikrotik's Routerboard 532.
config LEDS_MT6323
tristate "LED Support for Mediatek MT6323 PMIC"
depends on LEDS_CLASS
depends on MFD_MT6397
help
This option enables support for on-chip LED drivers found on
Mediatek MT6323 PMIC.
config LEDS_S3C24XX config LEDS_S3C24XX
tristate "LED Support for Samsung S3C24XX GPIO LEDs" tristate "LED Support for Samsung S3C24XX GPIO LEDs"
depends on LEDS_CLASS depends on LEDS_CLASS
...@@ -241,7 +258,6 @@ config LEDS_LP3952 ...@@ -241,7 +258,6 @@ config LEDS_LP3952
tristate "LED Support for TI LP3952 2 channel LED driver" tristate "LED Support for TI LP3952 2 channel LED driver"
depends on LEDS_CLASS depends on LEDS_CLASS
depends on I2C depends on I2C
depends on ACPI
depends on GPIOLIB depends on GPIOLIB
select REGMAP_I2C select REGMAP_I2C
help help
...@@ -463,15 +479,6 @@ config LEDS_ADP5520 ...@@ -463,15 +479,6 @@ config LEDS_ADP5520
To compile this driver as a module, choose M here: the module will To compile this driver as a module, choose M here: the module will
be called leds-adp5520. be called leds-adp5520.
config LEDS_DELL_NETBOOKS
tristate "External LED on Dell Business Netbooks"
depends on LEDS_CLASS
depends on X86 && ACPI_WMI
depends on DELL_SMBIOS
help
This adds support for the Latitude 2100 and similar
notebooks that have an external LED.
config LEDS_MC13783 config LEDS_MC13783
tristate "LED Support for MC13XXX PMIC" tristate "LED Support for MC13XXX PMIC"
depends on LEDS_CLASS depends on LEDS_CLASS
......
...@@ -11,6 +11,7 @@ obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o ...@@ -11,6 +11,7 @@ obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
obj-$(CONFIG_LEDS_CPCAP) += leds-cpcap.o
obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o
...@@ -52,7 +53,6 @@ obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o ...@@ -52,7 +53,6 @@ obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o
obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o
obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
obj-$(CONFIG_LEDS_DELL_NETBOOKS) += dell-led.o
obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
obj-$(CONFIG_LEDS_NS2) += leds-ns2.o obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
...@@ -72,6 +72,7 @@ obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o ...@@ -72,6 +72,7 @@ obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o
obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o
obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o
obj-$(CONFIG_LEDS_MT6323) += leds-mt6323.o
# LED SPI Drivers # LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
......
...@@ -244,11 +244,14 @@ static int led_classdev_next_name(const char *init_name, char *name, ...@@ -244,11 +244,14 @@ static int led_classdev_next_name(const char *init_name, char *name,
} }
/** /**
* led_classdev_register - register a new object of led_classdev class. * of_led_classdev_register - register a new object of led_classdev class.
* @parent: The device to register. *
* @parent: parent of LED device
* @led_cdev: the led_classdev structure for this device. * @led_cdev: the led_classdev structure for this device.
* @np: DT node describing this LED
*/ */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) int of_led_classdev_register(struct device *parent, struct device_node *np,
struct led_classdev *led_cdev)
{ {
char name[LED_MAX_NAME_SIZE]; char name[LED_MAX_NAME_SIZE];
int ret; int ret;
...@@ -261,6 +264,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) ...@@ -261,6 +264,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
led_cdev, led_cdev->groups, "%s", name); led_cdev, led_cdev->groups, "%s", name);
if (IS_ERR(led_cdev->dev)) if (IS_ERR(led_cdev->dev))
return PTR_ERR(led_cdev->dev); return PTR_ERR(led_cdev->dev);
led_cdev->dev->of_node = np;
if (ret) if (ret)
dev_warn(parent, "Led %s renamed to %s due to name collision", dev_warn(parent, "Led %s renamed to %s due to name collision",
...@@ -303,7 +307,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) ...@@ -303,7 +307,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(led_classdev_register); EXPORT_SYMBOL_GPL(of_led_classdev_register);
/** /**
* led_classdev_unregister - unregisters a object of led_properties class. * led_classdev_unregister - unregisters a object of led_properties class.
...@@ -348,12 +352,14 @@ static void devm_led_classdev_release(struct device *dev, void *res) ...@@ -348,12 +352,14 @@ static void devm_led_classdev_release(struct device *dev, void *res)
} }
/** /**
* devm_led_classdev_register - resource managed led_classdev_register() * devm_of_led_classdev_register - resource managed led_classdev_register()
* @parent: The device to register. *
* @parent: parent of LED device
* @led_cdev: the led_classdev structure for this device. * @led_cdev: the led_classdev structure for this device.
*/ */
int devm_led_classdev_register(struct device *parent, int devm_of_led_classdev_register(struct device *parent,
struct led_classdev *led_cdev) struct device_node *np,
struct led_classdev *led_cdev)
{ {
struct led_classdev **dr; struct led_classdev **dr;
int rc; int rc;
...@@ -362,7 +368,7 @@ int devm_led_classdev_register(struct device *parent, ...@@ -362,7 +368,7 @@ int devm_led_classdev_register(struct device *parent,
if (!dr) if (!dr)
return -ENOMEM; return -ENOMEM;
rc = led_classdev_register(parent, led_cdev); rc = of_led_classdev_register(parent, np, led_cdev);
if (rc) { if (rc) {
devres_free(dr); devres_free(dr);
return rc; return rc;
...@@ -373,7 +379,7 @@ int devm_led_classdev_register(struct device *parent, ...@@ -373,7 +379,7 @@ int devm_led_classdev_register(struct device *parent,
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(devm_led_classdev_register); EXPORT_SYMBOL_GPL(devm_of_led_classdev_register);
static int devm_led_classdev_match(struct device *dev, void *res, void *data) static int devm_led_classdev_match(struct device *dev, void *res, void *data)
{ {
......
/*
* Copyright (c) 2017 Sebastian Reichel <sre@kernel.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* later 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.
*/
#include <linux/leds.h>
#include <linux/mfd/motorola-cpcap.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#define CPCAP_LED_NO_CURRENT 0x0001
struct cpcap_led_info {
u16 reg;
u16 mask;
u16 limit;
u16 init_mask;
u16 init_val;
};
static const struct cpcap_led_info cpcap_led_red = {
.reg = CPCAP_REG_REDC,
.mask = 0x03FF,
.limit = 31,
};
static const struct cpcap_led_info cpcap_led_green = {
.reg = CPCAP_REG_GREENC,
.mask = 0x03FF,
.limit = 31,
};
static const struct cpcap_led_info cpcap_led_blue = {
.reg = CPCAP_REG_BLUEC,
.mask = 0x03FF,
.limit = 31,
};
/* aux display light */
static const struct cpcap_led_info cpcap_led_adl = {
.reg = CPCAP_REG_ADLC,
.mask = 0x000F,
.limit = 1,
.init_mask = 0x7FFF,
.init_val = 0x5FF0,
};
/* camera privacy led */
static const struct cpcap_led_info cpcap_led_cp = {
.reg = CPCAP_REG_CLEDC,
.mask = 0x0007,
.limit = 1,
.init_mask = 0x03FF,
.init_val = 0x0008,
};
struct cpcap_led {
struct led_classdev led;
const struct cpcap_led_info *info;
struct device *dev;
struct regmap *regmap;
struct mutex update_lock;
struct regulator *vdd;
bool powered;
u32 current_limit;
};
static u16 cpcap_led_val(u8 current_limit, u8 duty_cycle)
{
current_limit &= 0x1f; /* 5 bit */
duty_cycle &= 0x0f; /* 4 bit */
return current_limit << 4 | duty_cycle;
}
static int cpcap_led_set_power(struct cpcap_led *led, bool status)
{
int err;
if (status == led->powered)
return 0;
if (status)
err = regulator_enable(led->vdd);
else
err = regulator_disable(led->vdd);
if (err) {
dev_err(led->dev, "regulator failure: %d", err);
return err;
}
led->powered = status;
return 0;
}
static int cpcap_led_set(struct led_classdev *ledc, enum led_brightness value)
{
struct cpcap_led *led = container_of(ledc, struct cpcap_led, led);
int brightness;
int err;
mutex_lock(&led->update_lock);
if (value > LED_OFF) {
err = cpcap_led_set_power(led, true);
if (err)
goto exit;
}
if (value == LED_OFF) {
/* Avoid HW issue by turning off current before duty cycle */
err = regmap_update_bits(led->regmap,
led->info->reg, led->info->mask, CPCAP_LED_NO_CURRENT);
if (err) {
dev_err(led->dev, "regmap failed: %d", err);
goto exit;
}
brightness = cpcap_led_val(value, LED_OFF);
} else {
brightness = cpcap_led_val(value, LED_ON);
}
err = regmap_update_bits(led->regmap, led->info->reg, led->info->mask,
brightness);
if (err) {
dev_err(led->dev, "regmap failed: %d", err);
goto exit;
}
if (value == LED_OFF) {
err = cpcap_led_set_power(led, false);
if (err)
goto exit;
}
exit:
mutex_unlock(&led->update_lock);
return err;
}
static const struct of_device_id cpcap_led_of_match[] = {
{ .compatible = "motorola,cpcap-led-red", .data = &cpcap_led_red },
{ .compatible = "motorola,cpcap-led-green", .data = &cpcap_led_green },
{ .compatible = "motorola,cpcap-led-blue", .data = &cpcap_led_blue },
{ .compatible = "motorola,cpcap-led-adl", .data = &cpcap_led_adl },
{ .compatible = "motorola,cpcap-led-cp", .data = &cpcap_led_cp },
{},
};
MODULE_DEVICE_TABLE(of, cpcap_led_of_match);
static int cpcap_led_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct cpcap_led *led;
int err;
match = of_match_device(of_match_ptr(cpcap_led_of_match), &pdev->dev);
if (!match || !match->data)
return -EINVAL;
led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
platform_set_drvdata(pdev, led);
led->info = match->data;
led->dev = &pdev->dev;
if (led->info->reg == 0x0000) {
dev_err(led->dev, "Unsupported LED");
return -ENODEV;
}
led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!led->regmap)
return -ENODEV;
led->vdd = devm_regulator_get(&pdev->dev, "vdd");
if (IS_ERR(led->vdd)) {
err = PTR_ERR(led->vdd);
dev_err(led->dev, "Couldn't get regulator: %d", err);
return err;
}
err = device_property_read_string(&pdev->dev, "label", &led->led.name);
if (err) {
dev_err(led->dev, "Couldn't read LED label: %d", err);
return err;
}
if (led->info->init_mask) {
err = regmap_update_bits(led->regmap, led->info->reg,
led->info->init_mask, led->info->init_val);
if (err) {
dev_err(led->dev, "regmap failed: %d", err);
return err;
}
}
mutex_init(&led->update_lock);
led->led.max_brightness = led->info->limit;
led->led.brightness_set_blocking = cpcap_led_set;
err = devm_led_classdev_register(&pdev->dev, &led->led);
if (err) {
dev_err(led->dev, "Couldn't register LED: %d", err);
return err;
}
return 0;
}
static struct platform_driver cpcap_led_driver = {
.probe = cpcap_led_probe,
.driver = {
.name = "cpcap-led",
.of_match_table = cpcap_led_of_match,
},
};
module_platform_driver(cpcap_led_driver);
MODULE_DESCRIPTION("CPCAP LED driver");
MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
MODULE_LICENSE("GPL");
...@@ -77,7 +77,7 @@ static int gpio_blink_set(struct led_classdev *led_cdev, ...@@ -77,7 +77,7 @@ static int gpio_blink_set(struct led_classdev *led_cdev,
static int create_gpio_led(const struct gpio_led *template, static int create_gpio_led(const struct gpio_led *template,
struct gpio_led_data *led_dat, struct device *parent, struct gpio_led_data *led_dat, struct device *parent,
gpio_blink_set_t blink_set) struct device_node *np, gpio_blink_set_t blink_set)
{ {
int ret, state; int ret, state;
...@@ -139,7 +139,7 @@ static int create_gpio_led(const struct gpio_led *template, ...@@ -139,7 +139,7 @@ static int create_gpio_led(const struct gpio_led *template,
if (ret < 0) if (ret < 0)
return ret; return ret;
return devm_led_classdev_register(parent, &led_dat->cdev); return devm_of_led_classdev_register(parent, np, &led_dat->cdev);
} }
struct gpio_leds_priv { struct gpio_leds_priv {
...@@ -208,7 +208,7 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) ...@@ -208,7 +208,7 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
if (fwnode_property_present(child, "panic-indicator")) if (fwnode_property_present(child, "panic-indicator"))
led.panic_indicator = 1; led.panic_indicator = 1;
ret = create_gpio_led(&led, led_dat, dev, NULL); ret = create_gpio_led(&led, led_dat, dev, np, NULL);
if (ret < 0) { if (ret < 0) {
fwnode_handle_put(child); fwnode_handle_put(child);
return ERR_PTR(ret); return ERR_PTR(ret);
...@@ -242,9 +242,9 @@ static int gpio_led_probe(struct platform_device *pdev) ...@@ -242,9 +242,9 @@ static int gpio_led_probe(struct platform_device *pdev)
priv->num_leds = pdata->num_leds; priv->num_leds = pdata->num_leds;
for (i = 0; i < priv->num_leds; i++) { for (i = 0; i < priv->num_leds; i++) {
ret = create_gpio_led(&pdata->leds[i], ret = create_gpio_led(&pdata->leds[i], &priv->leds[i],
&priv->leds[i], &pdev->dev, NULL,
&pdev->dev, pdata->gpio_blink_set); pdata->gpio_blink_set);
if (ret < 0) if (ret < 0)
return ret; return ret;
} }
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
* *
*/ */
#include <linux/acpi.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/gpio.h> #include <linux/gpio.h>
#include <linux/i2c.h> #include <linux/i2c.h>
...@@ -103,10 +102,11 @@ static int lp3952_get_label(struct device *dev, const char *label, char *dest) ...@@ -103,10 +102,11 @@ static int lp3952_get_label(struct device *dev, const char *label, char *dest)
const char *str; const char *str;
ret = device_property_read_string(dev, label, &str); ret = device_property_read_string(dev, label, &str);
if (!ret) if (ret)
strncpy(dest, str, LP3952_LABEL_MAX_LEN); return ret;
return ret; strncpy(dest, str, LP3952_LABEL_MAX_LEN);
return 0;
} }
static int lp3952_register_led_classdev(struct lp3952_led_array *priv) static int lp3952_register_led_classdev(struct lp3952_led_array *priv)
...@@ -276,19 +276,9 @@ static const struct i2c_device_id lp3952_id[] = { ...@@ -276,19 +276,9 @@ static const struct i2c_device_id lp3952_id[] = {
}; };
MODULE_DEVICE_TABLE(i2c, lp3952_id); MODULE_DEVICE_TABLE(i2c, lp3952_id);
#ifdef CONFIG_ACPI
static const struct acpi_device_id lp3952_acpi_match[] = {
{"TXNW3952", 0},
{}
};
MODULE_DEVICE_TABLE(acpi, lp3952_acpi_match);
#endif
static struct i2c_driver lp3952_i2c_driver = { static struct i2c_driver lp3952_i2c_driver = {
.driver = { .driver = {
.name = LP3952_NAME, .name = LP3952_NAME,
.acpi_match_table = ACPI_PTR(lp3952_acpi_match),
}, },
.probe = lp3952_probe, .probe = lp3952_probe,
.remove = lp3952_remove, .remove = lp3952_remove,
......
/*
* LED driver for Mediatek MT6323 PMIC
*
* Copyright (C) 2017 Sean Wang <sean.wang@mediatek.com>
*
* 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; either version 2 of
* the License, or (at your option) any later version.
*
* 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/leds.h>
#include <linux/mfd/mt6323/registers.h>
#include <linux/mfd/mt6397/core.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
/*
* Register field for MT6323_TOP_CKPDN0 to enable
* 32K clock common for LED device.
*/
#define MT6323_RG_DRV_32K_CK_PDN BIT(11)
#define MT6323_RG_DRV_32K_CK_PDN_MASK BIT(11)
/*
* Register field for MT6323_TOP_CKPDN2 to enable
* individual clock for LED device.
*/
#define MT6323_RG_ISINK_CK_PDN(i) BIT(i)
#define MT6323_RG_ISINK_CK_PDN_MASK(i) BIT(i)
/*
* Register field for MT6323_TOP_CKCON1 to select
* clock source.
*/
#define MT6323_RG_ISINK_CK_SEL_MASK(i) (BIT(10) << (i))
/*
* Register for MT6323_ISINK_CON0 to setup the
* duty cycle of the blink.
*/
#define MT6323_ISINK_CON0(i) (MT6323_ISINK0_CON0 + 0x8 * (i))
#define MT6323_ISINK_DIM_DUTY_MASK (0x1f << 8)
#define MT6323_ISINK_DIM_DUTY(i) (((i) << 8) & \
MT6323_ISINK_DIM_DUTY_MASK)
/* Register to setup the period of the blink. */
#define MT6323_ISINK_CON1(i) (MT6323_ISINK0_CON1 + 0x8 * (i))
#define MT6323_ISINK_DIM_FSEL_MASK (0xffff)
#define MT6323_ISINK_DIM_FSEL(i) ((i) & MT6323_ISINK_DIM_FSEL_MASK)
/* Register to control the brightness. */
#define MT6323_ISINK_CON2(i) (MT6323_ISINK0_CON2 + 0x8 * (i))
#define MT6323_ISINK_CH_STEP_SHIFT 12
#define MT6323_ISINK_CH_STEP_MASK (0x7 << 12)
#define MT6323_ISINK_CH_STEP(i) (((i) << 12) & \
MT6323_ISINK_CH_STEP_MASK)
#define MT6323_ISINK_SFSTR0_TC_MASK (0x3 << 1)
#define MT6323_ISINK_SFSTR0_TC(i) (((i) << 1) & \
MT6323_ISINK_SFSTR0_TC_MASK)
#define MT6323_ISINK_SFSTR0_EN_MASK BIT(0)
#define MT6323_ISINK_SFSTR0_EN BIT(0)
/* Register to LED channel enablement. */
#define MT6323_ISINK_CH_EN_MASK(i) BIT(i)
#define MT6323_ISINK_CH_EN(i) BIT(i)
#define MT6323_MAX_PERIOD 10000
#define MT6323_MAX_LEDS 4
#define MT6323_MAX_BRIGHTNESS 6
#define MT6323_UNIT_DUTY 3125
#define MT6323_CAL_HW_DUTY(o, p) DIV_ROUND_CLOSEST((o) * 100000ul,\
(p) * MT6323_UNIT_DUTY)
struct mt6323_leds;
/**
* struct mt6323_led - state container for the LED device
* @id: the identifier in MT6323 LED device
* @parent: the pointer to MT6323 LED controller
* @cdev: LED class device for this LED device
* @current_brightness: current state of the LED device
*/
struct mt6323_led {
int id;
struct mt6323_leds *parent;
struct led_classdev cdev;
enum led_brightness current_brightness;
};
/**
* struct mt6323_leds - state container for holding LED controller
* of the driver
* @dev: the device pointer
* @hw: the underlying hardware providing shared
* bus for the register operations
* @lock: the lock among process context
* @led: the array that contains the state of individual
* LED device
*/
struct mt6323_leds {
struct device *dev;
struct mt6397_chip *hw;
/* protect among process context */
struct mutex lock;
struct mt6323_led *led[MT6323_MAX_LEDS];
};
static int mt6323_led_hw_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
struct regmap *regmap = leds->hw->regmap;
u32 con2_mask = 0, con2_val = 0;
int ret;
/*
* Setup current output for the corresponding
* brightness level.
*/
con2_mask |= MT6323_ISINK_CH_STEP_MASK |
MT6323_ISINK_SFSTR0_TC_MASK |
MT6323_ISINK_SFSTR0_EN_MASK;
con2_val |= MT6323_ISINK_CH_STEP(brightness - 1) |
MT6323_ISINK_SFSTR0_TC(2) |
MT6323_ISINK_SFSTR0_EN;
ret = regmap_update_bits(regmap, MT6323_ISINK_CON2(led->id),
con2_mask, con2_val);
return ret;
}
static int mt6323_led_hw_off(struct led_classdev *cdev)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
struct regmap *regmap = leds->hw->regmap;
unsigned int status;
int ret;
status = MT6323_ISINK_CH_EN(led->id);
ret = regmap_update_bits(regmap, MT6323_ISINK_EN_CTRL,
MT6323_ISINK_CH_EN_MASK(led->id), ~status);
if (ret < 0)
return ret;
usleep_range(100, 300);
ret = regmap_update_bits(regmap, MT6323_TOP_CKPDN2,
MT6323_RG_ISINK_CK_PDN_MASK(led->id),
MT6323_RG_ISINK_CK_PDN(led->id));
if (ret < 0)
return ret;
return 0;
}
static enum led_brightness
mt6323_get_led_hw_brightness(struct led_classdev *cdev)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
struct regmap *regmap = leds->hw->regmap;
unsigned int status;
int ret;
ret = regmap_read(regmap, MT6323_TOP_CKPDN2, &status);
if (ret < 0)
return ret;
if (status & MT6323_RG_ISINK_CK_PDN_MASK(led->id))
return 0;
ret = regmap_read(regmap, MT6323_ISINK_EN_CTRL, &status);
if (ret < 0)
return ret;
if (!(status & MT6323_ISINK_CH_EN(led->id)))
return 0;
ret = regmap_read(regmap, MT6323_ISINK_CON2(led->id), &status);
if (ret < 0)
return ret;
return ((status & MT6323_ISINK_CH_STEP_MASK)
>> MT6323_ISINK_CH_STEP_SHIFT) + 1;
}
static int mt6323_led_hw_on(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
struct regmap *regmap = leds->hw->regmap;
unsigned int status;
int ret;
/*
* Setup required clock source, enable the corresponding
* clock and channel and let work with continuous blink as
* the default.
*/
ret = regmap_update_bits(regmap, MT6323_TOP_CKCON1,
MT6323_RG_ISINK_CK_SEL_MASK(led->id), 0);
if (ret < 0)
return ret;
status = MT6323_RG_ISINK_CK_PDN(led->id);
ret = regmap_update_bits(regmap, MT6323_TOP_CKPDN2,
MT6323_RG_ISINK_CK_PDN_MASK(led->id),
~status);
if (ret < 0)
return ret;
usleep_range(100, 300);
ret = regmap_update_bits(regmap, MT6323_ISINK_EN_CTRL,
MT6323_ISINK_CH_EN_MASK(led->id),
MT6323_ISINK_CH_EN(led->id));
if (ret < 0)
return ret;
ret = mt6323_led_hw_brightness(cdev, brightness);
if (ret < 0)
return ret;
ret = regmap_update_bits(regmap, MT6323_ISINK_CON0(led->id),
MT6323_ISINK_DIM_DUTY_MASK,
MT6323_ISINK_DIM_DUTY(31));
if (ret < 0)
return ret;
ret = regmap_update_bits(regmap, MT6323_ISINK_CON1(led->id),
MT6323_ISINK_DIM_FSEL_MASK,
MT6323_ISINK_DIM_FSEL(1000));
if (ret < 0)
return ret;
return 0;
}
static int mt6323_led_set_blink(struct led_classdev *cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
struct regmap *regmap = leds->hw->regmap;
unsigned long period;
u8 duty_hw;
int ret;
/*
* Units are in ms, if over the hardware able
* to support, fallback into software blink
*/
period = *delay_on + *delay_off;
if (period > MT6323_MAX_PERIOD)
return -EINVAL;
/*
* LED subsystem requires a default user
* friendly blink pattern for the LED so using
* 1Hz duty cycle 50% here if without specific
* value delay_on and delay off being assigned.
*/
if (!*delay_on && !*delay_off) {
*delay_on = 500;
*delay_off = 500;
}
/*
* Calculate duty_hw based on the percentage of period during
* which the led is ON.
*/
duty_hw = MT6323_CAL_HW_DUTY(*delay_on, period);
/* hardware doesn't support zero duty cycle. */
if (!duty_hw)
return -EINVAL;
mutex_lock(&leds->lock);
/*
* Set max_brightness as the software blink behavior
* when no blink brightness.
*/
if (!led->current_brightness) {
ret = mt6323_led_hw_on(cdev, cdev->max_brightness);
if (ret < 0)
goto out;
led->current_brightness = cdev->max_brightness;
}
ret = regmap_update_bits(regmap, MT6323_ISINK_CON0(led->id),
MT6323_ISINK_DIM_DUTY_MASK,
MT6323_ISINK_DIM_DUTY(duty_hw - 1));
if (ret < 0)
goto out;
ret = regmap_update_bits(regmap, MT6323_ISINK_CON1(led->id),
MT6323_ISINK_DIM_FSEL_MASK,
MT6323_ISINK_DIM_FSEL(period - 1));
out:
mutex_unlock(&leds->lock);
return ret;
}
static int mt6323_led_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
int ret;
mutex_lock(&leds->lock);
if (!led->current_brightness && brightness) {
ret = mt6323_led_hw_on(cdev, brightness);
if (ret < 0)
goto out;
} else if (brightness) {
ret = mt6323_led_hw_brightness(cdev, brightness);
if (ret < 0)
goto out;
} else {
ret = mt6323_led_hw_off(cdev);
if (ret < 0)
goto out;
}
led->current_brightness = brightness;
out:
mutex_unlock(&leds->lock);
return ret;
}
static int mt6323_led_set_dt_default(struct led_classdev *cdev,
struct device_node *np)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
const char *state;
int ret = 0;
led->cdev.name = of_get_property(np, "label", NULL) ? : np->name;
led->cdev.default_trigger = of_get_property(np,
"linux,default-trigger",
NULL);
state = of_get_property(np, "default-state", NULL);
if (state) {
if (!strcmp(state, "keep")) {
ret = mt6323_get_led_hw_brightness(cdev);
if (ret < 0)
return ret;
led->current_brightness = ret;
ret = 0;
} else if (!strcmp(state, "on")) {
ret =
mt6323_led_set_brightness(cdev, cdev->max_brightness);
} else {
ret = mt6323_led_set_brightness(cdev, LED_OFF);
}
}
return ret;
}
static int mt6323_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct device_node *child;
struct mt6397_chip *hw = dev_get_drvdata(pdev->dev.parent);
struct mt6323_leds *leds;
struct mt6323_led *led;
int ret;
unsigned int status;
u32 reg;
leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
if (!leds)
return -ENOMEM;
platform_set_drvdata(pdev, leds);
leds->dev = dev;
/*
* leds->hw points to the underlying bus for the register
* controlled.
*/
leds->hw = hw;
mutex_init(&leds->lock);
status = MT6323_RG_DRV_32K_CK_PDN;
ret = regmap_update_bits(leds->hw->regmap, MT6323_TOP_CKPDN0,
MT6323_RG_DRV_32K_CK_PDN_MASK, ~status);
if (ret < 0) {
dev_err(leds->dev,
"Failed to update MT6323_TOP_CKPDN0 Register\n");
return ret;
}
for_each_available_child_of_node(np, child) {
ret = of_property_read_u32(child, "reg", &reg);
if (ret) {
dev_err(dev, "Failed to read led 'reg' property\n");
goto put_child_node;
}
if (reg >= MT6323_MAX_LEDS || leds->led[reg]) {
dev_err(dev, "Invalid led reg %u\n", reg);
ret = -EINVAL;
goto put_child_node;
}
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led) {
ret = -ENOMEM;
goto put_child_node;
}
leds->led[reg] = led;
leds->led[reg]->id = reg;
leds->led[reg]->cdev.max_brightness = MT6323_MAX_BRIGHTNESS;
leds->led[reg]->cdev.brightness_set_blocking =
mt6323_led_set_brightness;
leds->led[reg]->cdev.blink_set = mt6323_led_set_blink;
leds->led[reg]->cdev.brightness_get =
mt6323_get_led_hw_brightness;
leds->led[reg]->parent = leds;
ret = mt6323_led_set_dt_default(&leds->led[reg]->cdev, child);
if (ret < 0) {
dev_err(leds->dev,
"Failed to LED set default from devicetree\n");
goto put_child_node;
}
ret = devm_led_classdev_register(dev, &leds->led[reg]->cdev);
if (ret) {
dev_err(&pdev->dev, "Failed to register LED: %d\n",
ret);
goto put_child_node;
}
leds->led[reg]->cdev.dev->of_node = child;
}
return 0;
put_child_node:
of_node_put(child);
return ret;
}
static int mt6323_led_remove(struct platform_device *pdev)
{
struct mt6323_leds *leds = platform_get_drvdata(pdev);
int i;
/* Turn the LEDs off on driver removal. */
for (i = 0 ; leds->led[i] ; i++)
mt6323_led_hw_off(&leds->led[i]->cdev);
regmap_update_bits(leds->hw->regmap, MT6323_TOP_CKPDN0,
MT6323_RG_DRV_32K_CK_PDN_MASK,
MT6323_RG_DRV_32K_CK_PDN);
mutex_destroy(&leds->lock);
return 0;
}
static const struct of_device_id mt6323_led_dt_match[] = {
{ .compatible = "mediatek,mt6323-led" },
{},
};
MODULE_DEVICE_TABLE(of, mt6323_led_dt_match);
static struct platform_driver mt6323_led_driver = {
.probe = mt6323_led_probe,
.remove = mt6323_led_remove,
.driver = {
.name = "mt6323-led",
.of_match_table = mt6323_led_dt_match,
},
};
module_platform_driver(mt6323_led_driver);
MODULE_DESCRIPTION("LED driver for Mediatek MT6323 PMIC");
MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");
MODULE_LICENSE("GPL");
...@@ -254,6 +254,21 @@ static void pca9532_input_work(struct work_struct *work) ...@@ -254,6 +254,21 @@ static void pca9532_input_work(struct work_struct *work)
mutex_unlock(&data->update_lock); mutex_unlock(&data->update_lock);
} }
static enum pca9532_state pca9532_getled(struct pca9532_led *led)
{
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
char reg;
enum pca9532_state ret;
mutex_lock(&data->update_lock);
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
ret = reg >> LED_NUM(led->id)/2;
mutex_unlock(&data->update_lock);
return ret;
}
#ifdef CONFIG_LEDS_PCA9532_GPIO #ifdef CONFIG_LEDS_PCA9532_GPIO
static int pca9532_gpio_request_pin(struct gpio_chip *gc, unsigned offset) static int pca9532_gpio_request_pin(struct gpio_chip *gc, unsigned offset)
{ {
...@@ -366,7 +381,10 @@ static int pca9532_configure(struct i2c_client *client, ...@@ -366,7 +381,10 @@ static int pca9532_configure(struct i2c_client *client,
gpios++; gpios++;
break; break;
case PCA9532_TYPE_LED: case PCA9532_TYPE_LED:
led->state = pled->state; if (pled->state == PCA9532_KEEP)
led->state = pca9532_getled(led);
else
led->state = pled->state;
led->name = pled->name; led->name = pled->name;
led->ldev.name = led->name; led->ldev.name = led->name;
led->ldev.default_trigger = pled->default_trigger; led->ldev.default_trigger = pled->default_trigger;
...@@ -456,6 +474,7 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np) ...@@ -456,6 +474,7 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np)
const struct of_device_id *match; const struct of_device_id *match;
int devid, maxleds; int devid, maxleds;
int i = 0; int i = 0;
const char *state;
match = of_match_device(of_pca9532_leds_match, dev); match = of_match_device(of_pca9532_leds_match, dev);
if (!match) if (!match)
...@@ -475,6 +494,12 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np) ...@@ -475,6 +494,12 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np)
of_property_read_u32(child, "type", &pdata->leds[i].type); of_property_read_u32(child, "type", &pdata->leds[i].type);
of_property_read_string(child, "linux,default-trigger", of_property_read_string(child, "linux,default-trigger",
&pdata->leds[i].default_trigger); &pdata->leds[i].default_trigger);
if (!of_property_read_string(child, "default-state", &state)) {
if (!strcmp(state, "on"))
pdata->leds[i].state = PCA9532_ON;
else if (!strcmp(state, "keep"))
pdata->leds[i].state = PCA9532_KEEP;
}
if (++i >= maxleds) { if (++i >= maxleds) {
of_node_put(child); of_node_put(child);
break; break;
......
...@@ -31,12 +31,16 @@ ...@@ -31,12 +31,16 @@
#define MAX_NAME_LEN 8 #define MAX_NAME_LEN 8
struct led_trigger_cpu { struct led_trigger_cpu {
bool is_active;
char name[MAX_NAME_LEN]; char name[MAX_NAME_LEN];
struct led_trigger *_trig; struct led_trigger *_trig;
}; };
static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig);
static struct led_trigger *trig_cpu_all;
static atomic_t num_active_cpus = ATOMIC_INIT(0);
/** /**
* ledtrig_cpu - emit a CPU event as a trigger * ledtrig_cpu - emit a CPU event as a trigger
* @evt: CPU event to be emitted * @evt: CPU event to be emitted
...@@ -47,26 +51,46 @@ static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); ...@@ -47,26 +51,46 @@ static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig);
void ledtrig_cpu(enum cpu_led_event ledevt) void ledtrig_cpu(enum cpu_led_event ledevt)
{ {
struct led_trigger_cpu *trig = this_cpu_ptr(&cpu_trig); struct led_trigger_cpu *trig = this_cpu_ptr(&cpu_trig);
bool is_active = trig->is_active;
/* Locate the correct CPU LED */ /* Locate the correct CPU LED */
switch (ledevt) { switch (ledevt) {
case CPU_LED_IDLE_END: case CPU_LED_IDLE_END:
case CPU_LED_START: case CPU_LED_START:
/* Will turn the LED on, max brightness */ /* Will turn the LED on, max brightness */
led_trigger_event(trig->_trig, LED_FULL); is_active = true;
break; break;
case CPU_LED_IDLE_START: case CPU_LED_IDLE_START:
case CPU_LED_STOP: case CPU_LED_STOP:
case CPU_LED_HALTED: case CPU_LED_HALTED:
/* Will turn the LED off */ /* Will turn the LED off */
led_trigger_event(trig->_trig, LED_OFF); is_active = false;
break; break;
default: default:
/* Will leave the LED as it is */ /* Will leave the LED as it is */
break; break;
} }
if (is_active != trig->is_active) {
unsigned int active_cpus;
unsigned int total_cpus;
/* Update trigger state */
trig->is_active = is_active;
atomic_add(is_active ? 1 : -1, &num_active_cpus);
active_cpus = atomic_read(&num_active_cpus);
total_cpus = num_present_cpus();
led_trigger_event(trig->_trig,
is_active ? LED_FULL : LED_OFF);
led_trigger_event(trig_cpu_all,
DIV_ROUND_UP(LED_FULL * active_cpus, total_cpus));
}
} }
EXPORT_SYMBOL(ledtrig_cpu); EXPORT_SYMBOL(ledtrig_cpu);
...@@ -112,6 +136,11 @@ static int __init ledtrig_cpu_init(void) ...@@ -112,6 +136,11 @@ static int __init ledtrig_cpu_init(void)
/* Supports up to 9999 cpu cores */ /* Supports up to 9999 cpu cores */
BUILD_BUG_ON(CONFIG_NR_CPUS > 9999); BUILD_BUG_ON(CONFIG_NR_CPUS > 9999);
/*
* Registering a trigger for all CPUs.
*/
led_trigger_register_simple("cpu", &trig_cpu_all);
/* /*
* Registering CPU led trigger for each CPU core here * Registering CPU led trigger for each CPU core here
* ignores CPU hotplug, but after this CPU hotplug works * ignores CPU hotplug, but after this CPU hotplug works
......
...@@ -141,6 +141,14 @@ config DELL_WMI_AIO ...@@ -141,6 +141,14 @@ config DELL_WMI_AIO
To compile this driver as a module, choose M here: the module will To compile this driver as a module, choose M here: the module will
be called dell-wmi-aio. be called dell-wmi-aio.
config DELL_WMI_LED
tristate "External LED on Dell Business Netbooks"
depends on LEDS_CLASS
depends on ACPI_WMI
help
This adds support for the Latitude 2100 and similar
notebooks that have an external LED.
config DELL_SMO8800 config DELL_SMO8800
tristate "Dell Latitude freefall driver (ACPI SMO88XX)" tristate "Dell Latitude freefall driver (ACPI SMO88XX)"
depends on ACPI depends on ACPI
......
...@@ -15,6 +15,7 @@ obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o ...@@ -15,6 +15,7 @@ obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o
obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o
obj-$(CONFIG_DELL_WMI) += dell-wmi.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o
obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o
obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o
obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o
obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o
obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/i8042.h> #include <linux/i8042.h>
#include <linux/debugfs.h> #include <linux/debugfs.h>
#include <linux/dell-led.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
#include <acpi/video.h> #include <acpi/video.h>
#include "dell-rbtn.h" #include "dell-rbtn.h"
...@@ -42,6 +43,8 @@ ...@@ -42,6 +43,8 @@
#define KBD_LED_AUTO_50_TOKEN 0x02EB #define KBD_LED_AUTO_50_TOKEN 0x02EB
#define KBD_LED_AUTO_75_TOKEN 0x02EC #define KBD_LED_AUTO_75_TOKEN 0x02EC
#define KBD_LED_AUTO_100_TOKEN 0x02F6 #define KBD_LED_AUTO_100_TOKEN 0x02F6
#define GLOBAL_MIC_MUTE_ENABLE 0x0364
#define GLOBAL_MIC_MUTE_DISABLE 0x0365
struct quirk_entry { struct quirk_entry {
u8 touchpad_led; u8 touchpad_led;
...@@ -1978,6 +1981,31 @@ static void kbd_led_exit(void) ...@@ -1978,6 +1981,31 @@ static void kbd_led_exit(void)
led_classdev_unregister(&kbd_led); led_classdev_unregister(&kbd_led);
} }
int dell_micmute_led_set(int state)
{
struct calling_interface_buffer *buffer;
struct calling_interface_token *token;
if (state == 0)
token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE);
else if (state == 1)
token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE);
else
return -EINVAL;
if (!token)
return -ENODEV;
buffer = dell_smbios_get_buffer();
buffer->input[0] = token->location;
buffer->input[1] = token->value;
dell_smbios_send_request(1, 0);
dell_smbios_release_buffer();
return state;
}
EXPORT_SYMBOL_GPL(dell_micmute_led_set);
static int __init dell_init(void) static int __init dell_init(void)
{ {
struct calling_interface_buffer *buffer; struct calling_interface_buffer *buffer;
......
/* /*
* dell_led.c - Dell LED Driver
*
* Copyright (C) 2010 Dell Inc. * Copyright (C) 2010 Dell Inc.
* Louis Davis <louis_davis@dell.com> * Louis Davis <louis_davis@dell.com>
* Jim Dailey <jim_dailey@dell.com> * Jim Dailey <jim_dailey@dell.com>
...@@ -15,16 +13,12 @@ ...@@ -15,16 +13,12 @@
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/dmi.h>
#include <linux/dell-led.h>
#include "../platform/x86/dell-smbios.h"
MODULE_AUTHOR("Louis Davis/Jim Dailey"); MODULE_AUTHOR("Louis Davis/Jim Dailey");
MODULE_DESCRIPTION("Dell LED Control Driver"); MODULE_DESCRIPTION("Dell LED Control Driver");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396" #define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396"
#define DELL_APP_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID);
/* Error Result Codes: */ /* Error Result Codes: */
...@@ -43,53 +37,6 @@ MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); ...@@ -43,53 +37,6 @@ MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID);
#define CMD_LED_OFF 17 #define CMD_LED_OFF 17
#define CMD_LED_BLINK 18 #define CMD_LED_BLINK 18
#define GLOBAL_MIC_MUTE_ENABLE 0x364
#define GLOBAL_MIC_MUTE_DISABLE 0x365
static int dell_micmute_led_set(int state)
{
struct calling_interface_buffer *buffer;
struct calling_interface_token *token;
if (!wmi_has_guid(DELL_APP_GUID))
return -ENODEV;
if (state == 0)
token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE);
else if (state == 1)
token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE);
else
return -EINVAL;
if (!token)
return -ENODEV;
buffer = dell_smbios_get_buffer();
buffer->input[0] = token->location;
buffer->input[1] = token->value;
dell_smbios_send_request(1, 0);
dell_smbios_release_buffer();
return state;
}
int dell_app_wmi_led_set(int whichled, int on)
{
int state = 0;
switch (whichled) {
case DELL_LED_MICMUTE:
state = dell_micmute_led_set(on);
break;
default:
pr_warn("led type %x is not supported\n", whichled);
break;
}
return state;
}
EXPORT_SYMBOL_GPL(dell_app_wmi_led_set);
struct bios_args { struct bios_args {
unsigned char length; unsigned char length;
unsigned char result_code; unsigned char result_code;
...@@ -99,37 +46,29 @@ struct bios_args { ...@@ -99,37 +46,29 @@ struct bios_args {
unsigned char off_time; unsigned char off_time;
}; };
static int dell_led_perform_fn(u8 length, static int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id,
u8 result_code, u8 command, u8 on_time, u8 off_time)
u8 device_id,
u8 command,
u8 on_time,
u8 off_time)
{ {
struct bios_args *bios_return;
u8 return_code;
union acpi_object *obj;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
struct bios_args *bios_return;
struct acpi_buffer input; struct acpi_buffer input;
union acpi_object *obj;
acpi_status status; acpi_status status;
u8 return_code;
struct bios_args args; struct bios_args args = {
args.length = length; .length = length,
args.result_code = result_code; .result_code = result_code,
args.device_id = device_id; .device_id = device_id,
args.command = command; .command = command,
args.on_time = on_time; .on_time = on_time,
args.off_time = off_time; .off_time = off_time
};
input.length = sizeof(struct bios_args); input.length = sizeof(struct bios_args);
input.pointer = &args; input.pointer = &args;
status = wmi_evaluate_method(DELL_LED_BIOS_GUID, status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 1, 1, &input, &output);
1,
1,
&input,
&output);
if (ACPI_FAILURE(status)) if (ACPI_FAILURE(status))
return status; return status;
...@@ -137,7 +76,7 @@ static int dell_led_perform_fn(u8 length, ...@@ -137,7 +76,7 @@ static int dell_led_perform_fn(u8 length,
if (!obj) if (!obj)
return -EINVAL; return -EINVAL;
else if (obj->type != ACPI_TYPE_BUFFER) { if (obj->type != ACPI_TYPE_BUFFER) {
kfree(obj); kfree(obj);
return -EINVAL; return -EINVAL;
} }
...@@ -170,8 +109,7 @@ static int led_off(void) ...@@ -170,8 +109,7 @@ static int led_off(void)
0); /* not used */ 0); /* not used */
} }
static int led_blink(unsigned char on_eighths, static int led_blink(unsigned char on_eighths, unsigned char off_eighths)
unsigned char off_eighths)
{ {
return dell_led_perform_fn(5, /* Length of command */ return dell_led_perform_fn(5, /* Length of command */
INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ INTERFACE_ERROR, /* Init to INTERFACE_ERROR */
...@@ -182,7 +120,7 @@ static int led_blink(unsigned char on_eighths, ...@@ -182,7 +120,7 @@ static int led_blink(unsigned char on_eighths,
} }
static void dell_led_set(struct led_classdev *led_cdev, static void dell_led_set(struct led_classdev *led_cdev,
enum led_brightness value) enum led_brightness value)
{ {
if (value == LED_OFF) if (value == LED_OFF)
led_off(); led_off();
...@@ -191,27 +129,22 @@ static void dell_led_set(struct led_classdev *led_cdev, ...@@ -191,27 +129,22 @@ static void dell_led_set(struct led_classdev *led_cdev,
} }
static int dell_led_blink(struct led_classdev *led_cdev, static int dell_led_blink(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_on, unsigned long *delay_off)
unsigned long *delay_off)
{ {
unsigned long on_eighths; unsigned long on_eighths;
unsigned long off_eighths; unsigned long off_eighths;
/* The Dell LED delay is based on 125ms intervals. /*
Need to round up to next interval. */ * The Dell LED delay is based on 125ms intervals.
* Need to round up to next interval.
*/
on_eighths = (*delay_on + 124) / 125; on_eighths = DIV_ROUND_UP(*delay_on, 125);
if (0 == on_eighths) on_eighths = clamp_t(unsigned long, on_eighths, 1, 255);
on_eighths = 1;
if (on_eighths > 255)
on_eighths = 255;
*delay_on = on_eighths * 125; *delay_on = on_eighths * 125;
off_eighths = (*delay_off + 124) / 125; off_eighths = DIV_ROUND_UP(*delay_off, 125);
if (0 == off_eighths) off_eighths = clamp_t(unsigned long, off_eighths, 1, 255);
off_eighths = 1;
if (off_eighths > 255)
off_eighths = 255;
*delay_off = off_eighths * 125; *delay_off = off_eighths * 125;
led_blink(on_eighths, off_eighths); led_blink(on_eighths, off_eighths);
...@@ -232,29 +165,21 @@ static int __init dell_led_init(void) ...@@ -232,29 +165,21 @@ static int __init dell_led_init(void)
{ {
int error = 0; int error = 0;
if (!wmi_has_guid(DELL_LED_BIOS_GUID) && !wmi_has_guid(DELL_APP_GUID)) if (!wmi_has_guid(DELL_LED_BIOS_GUID))
return -ENODEV; return -ENODEV;
if (wmi_has_guid(DELL_LED_BIOS_GUID)) { error = led_off();
error = led_off(); if (error != 0)
if (error != 0) return -ENODEV;
return -ENODEV;
error = led_classdev_register(NULL, &dell_led);
}
return error; return led_classdev_register(NULL, &dell_led);
} }
static void __exit dell_led_exit(void) static void __exit dell_led_exit(void)
{ {
int error = 0; led_classdev_unregister(&dell_led);
if (wmi_has_guid(DELL_LED_BIOS_GUID)) { led_off();
error = led_off();
if (error == 0)
led_classdev_unregister(&dell_led);
}
} }
module_init(dell_led_init); module_init(dell_led_init);
......
#ifndef __DELL_LED_H__ #ifndef __DELL_LED_H__
#define __DELL_LED_H__ #define __DELL_LED_H__
enum { int dell_micmute_led_set(int on);
DELL_LED_MICMUTE,
};
int dell_app_wmi_led_set(int whichled, int on);
#endif #endif
...@@ -22,7 +22,8 @@ enum pca9532_state { ...@@ -22,7 +22,8 @@ enum pca9532_state {
PCA9532_OFF = 0x0, PCA9532_OFF = 0x0,
PCA9532_ON = 0x1, PCA9532_ON = 0x1,
PCA9532_PWM0 = 0x2, PCA9532_PWM0 = 0x2,
PCA9532_PWM1 = 0x3 PCA9532_PWM1 = 0x3,
PCA9532_KEEP = 0xff,
}; };
struct pca9532_led { struct pca9532_led {
...@@ -44,4 +45,3 @@ struct pca9532_platform_data { ...@@ -44,4 +45,3 @@ struct pca9532_platform_data {
}; };
#endif /* __LINUX_PCA9532_H */ #endif /* __LINUX_PCA9532_H */
...@@ -122,10 +122,16 @@ struct led_classdev { ...@@ -122,10 +122,16 @@ struct led_classdev {
struct mutex led_access; struct mutex led_access;
}; };
extern int led_classdev_register(struct device *parent, extern int of_led_classdev_register(struct device *parent,
struct led_classdev *led_cdev); struct device_node *np,
extern int devm_led_classdev_register(struct device *parent, struct led_classdev *led_cdev);
struct led_classdev *led_cdev); #define led_classdev_register(parent, led_cdev) \
of_led_classdev_register(parent, NULL, led_cdev)
extern int devm_of_led_classdev_register(struct device *parent,
struct device_node *np,
struct led_classdev *led_cdev);
#define devm_led_classdev_register(parent, led_cdev) \
devm_of_led_classdev_register(parent, NULL, led_cdev)
extern void led_classdev_unregister(struct led_classdev *led_cdev); extern void led_classdev_unregister(struct led_classdev *led_cdev);
extern void devm_led_classdev_unregister(struct device *parent, extern void devm_led_classdev_unregister(struct device *parent,
struct led_classdev *led_cdev); struct led_classdev *led_cdev);
......
...@@ -14,6 +14,9 @@ ...@@ -14,6 +14,9 @@
* published by the Free Software Foundation. * published by the Free Software Foundation.
*/ */
#include <linux/device.h>
#include <linux/regmap.h>
#define CPCAP_VENDOR_ST 0 #define CPCAP_VENDOR_ST 0
#define CPCAP_VENDOR_TI 1 #define CPCAP_VENDOR_TI 1
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
* to be included from codec driver * to be included from codec driver
*/ */
#if IS_ENABLED(CONFIG_LEDS_DELL_NETBOOKS) #if IS_ENABLED(CONFIG_DELL_LAPTOP)
#include <linux/dell-led.h> #include <linux/dell-led.h>
static int dell_led_value; static int dell_led_value;
static int (*dell_led_set_func)(int, int); static int (*dell_micmute_led_set_func)(int);
static void (*dell_old_cap_hook)(struct hda_codec *, static void (*dell_old_cap_hook)(struct hda_codec *,
struct snd_kcontrol *, struct snd_kcontrol *,
struct snd_ctl_elem_value *); struct snd_ctl_elem_value *);
...@@ -18,7 +18,7 @@ static void update_dell_wmi_micmute_led(struct hda_codec *codec, ...@@ -18,7 +18,7 @@ static void update_dell_wmi_micmute_led(struct hda_codec *codec,
if (dell_old_cap_hook) if (dell_old_cap_hook)
dell_old_cap_hook(codec, kcontrol, ucontrol); dell_old_cap_hook(codec, kcontrol, ucontrol);
if (!ucontrol || !dell_led_set_func) if (!ucontrol || !dell_micmute_led_set_func)
return; return;
if (strcmp("Capture Switch", ucontrol->id.name) == 0 && ucontrol->id.index == 0) { if (strcmp("Capture Switch", ucontrol->id.name) == 0 && ucontrol->id.index == 0) {
/* TODO: How do I verify if it's a mono or stereo here? */ /* TODO: How do I verify if it's a mono or stereo here? */
...@@ -26,8 +26,8 @@ static void update_dell_wmi_micmute_led(struct hda_codec *codec, ...@@ -26,8 +26,8 @@ static void update_dell_wmi_micmute_led(struct hda_codec *codec,
if (val == dell_led_value) if (val == dell_led_value)
return; return;
dell_led_value = val; dell_led_value = val;
if (dell_led_set_func) if (dell_micmute_led_set_func)
dell_led_set_func(DELL_LED_MICMUTE, dell_led_value); dell_micmute_led_set_func(dell_led_value);
} }
} }
...@@ -39,15 +39,15 @@ static void alc_fixup_dell_wmi(struct hda_codec *codec, ...@@ -39,15 +39,15 @@ static void alc_fixup_dell_wmi(struct hda_codec *codec,
bool removefunc = false; bool removefunc = false;
if (action == HDA_FIXUP_ACT_PROBE) { if (action == HDA_FIXUP_ACT_PROBE) {
if (!dell_led_set_func) if (!dell_micmute_led_set_func)
dell_led_set_func = symbol_request(dell_app_wmi_led_set); dell_micmute_led_set_func = symbol_request(dell_micmute_led_set);
if (!dell_led_set_func) { if (!dell_micmute_led_set_func) {
codec_warn(codec, "Failed to find dell wmi symbol dell_app_wmi_led_set\n"); codec_warn(codec, "Failed to find dell wmi symbol dell_micmute_led_set\n");
return; return;
} }
removefunc = true; removefunc = true;
if (dell_led_set_func(DELL_LED_MICMUTE, false) >= 0) { if (dell_micmute_led_set_func(false) >= 0) {
dell_led_value = 0; dell_led_value = 0;
if (spec->gen.num_adc_nids > 1 && !spec->gen.dyn_adc_switch) if (spec->gen.num_adc_nids > 1 && !spec->gen.dyn_adc_switch)
codec_dbg(codec, "Skipping micmute LED control due to several ADCs"); codec_dbg(codec, "Skipping micmute LED control due to several ADCs");
...@@ -60,17 +60,17 @@ static void alc_fixup_dell_wmi(struct hda_codec *codec, ...@@ -60,17 +60,17 @@ static void alc_fixup_dell_wmi(struct hda_codec *codec,
} }
if (dell_led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) { if (dell_micmute_led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) {
symbol_put(dell_app_wmi_led_set); symbol_put(dell_micmute_led_set);
dell_led_set_func = NULL; dell_micmute_led_set_func = NULL;
dell_old_cap_hook = NULL; dell_old_cap_hook = NULL;
} }
} }
#else /* CONFIG_LEDS_DELL_NETBOOKS */ #else /* CONFIG_DELL_LAPTOP */
static void alc_fixup_dell_wmi(struct hda_codec *codec, static void alc_fixup_dell_wmi(struct hda_codec *codec,
const struct hda_fixup *fix, int action) const struct hda_fixup *fix, int action)
{ {
} }
#endif /* CONFIG_LEDS_DELL_NETBOOKS */ #endif /* CONFIG_DELL_LAPTOP */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment