Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0ead6f3a authored by Jayden Cha's avatar Jayden Cha Committed by Gerrit - the friendly Code Review server
Browse files

leds: PCA9955B: 24-channel constant current LED driver



The PCA9956B is an I2C bus controlled 24-channel LED driver.
Each LED output has its own 8-bit fixed-frequency PWM controller
to control the brightness of the LED.

Change-Id: I76b5d2ff08e72a2a1797bae61c027df5d02c1391
Signed-off-by: default avatarJayden Cha <jayden.cha@nxp.com>
Git-commit: 5dcf8ffdfb7f622b32a54e1ad9d42a0e7f8b2217
Git-repo: https://source.codeaurora.org/external/mob-pwr-pl/mpp/ldd/commit/pca9956b?h=pca9956b.rpi.linux-4.9.41


[pvadde@codeaurora.org: Making it as a built-in module]
Signed-off-by: default avatarPrashanth Vadde <pvadde@codeaurora.org>
parent c40e1092
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
NXP LED segment driver devicetree bindings

Required properties:

 - compatible               : "nxp,pca9956b" for PCA9956b
 - nxp,mode1                : MODE1 register
 - nxp,mode2                : MODE2 register
 - nxp,ledout0~5            : LED output state registers
 - nxp,defaultiref          : LED output gain control default value

Optional properties:

Example:
pca9956b {
	/* I2C version */
	reg = <0x7d>;

	/* nxp-ledseg properties */

	compatible    = "nxp,pca9956b";

	pinctrl-names = "default";
	pinctrl-0     = <&pca9956b_pins>;

	pca9956b,support_initialize = <1>;
	pca9956b,mode1 = <0x09>;
	pca9956b,mode2 = <0x05>;

	pca9956b,ledout0 = <0xaa>;
	pca9956b,ledout1 = <0xaa>;
	pca9956b,ledout2 = <0xaa>;
	pca9956b,ledout3 = <0xff>;
	pca9956b,ledout4 = <0xff>;
	pca9956b,ledout5 = <0xff>;
	pca9956b,defaultiref = <0x2f>;
};
+9 −0
Original line number Diff line number Diff line
@@ -299,6 +299,15 @@ config LEDS_PCA963X
	  LED driver chip accessed via the I2C bus. Supported
	  devices include PCA9633 and PCA9634

config LEDS_PCA9956B
	tristate "LED Support for PCA9956B I2C chips"
	depends on LEDS_CLASS
	depends on I2C
	help
	  This option enables support for LEDs connected to PCA9956B
	  LED driver chips accessed via the I2C bus. Supported
	  devices include PCA9956B.

config LEDS_WM831X_STATUS
	tristate "LED support for status LEDs on WM831x PMICs"
	depends on LEDS_CLASS
+1 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ obj-$(CONFIG_LEDS_OT200) += leds-ot200.o
obj-$(CONFIG_LEDS_FSG)			+= leds-fsg.o
obj-$(CONFIG_LEDS_PCA955X)		+= leds-pca955x.o
obj-$(CONFIG_LEDS_PCA963X)		+= leds-pca963x.o
obj-$(CONFIG_LEDS_PCA9956B)		+= leds-pca9956b.o
obj-$(CONFIG_LEDS_DA903X)		+= leds-da903x.o
obj-$(CONFIG_LEDS_DA9052)		+= leds-da9052.o
obj-$(CONFIG_LEDS_WM831X_STATUS)	+= leds-wm831x-status.o
+533 −0
Original line number Diff line number Diff line
/*
 * leds-pca9956b.c - NXP PCA9956B LED segment driver
 *
 * Copyright (C) 2017 NXP Semiconductors
 *
 * 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/module.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/leds.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include "leds-pca9956b.h"

#define PCA9956B_LED_NUM	24
#define MAX_DEVICES		32

#define DRIVER_NAME		"nxp-ledseg"
#define DRIVER_VERSION		"17.11.28"

struct pca9956b_chip {
	struct i2c_client *client;
	struct mutex lock;
	struct pca9956b_led *leds;
};

struct pca9956b_led {
	struct  led_classdev led_cdev;
	struct  pca9956b_chip *chip;
	int	 led_num;
	char	name[32];
};

static struct class *nxp_ledseg_class;
static struct device *pca9956b_dev;

/*
 * Read one byte from given register address.
 */
static int pca9956b_read_reg(struct pca9956b_chip *chip, int reg, uint8_t *val)
{
	int ret = i2c_smbus_read_byte_data(chip->client, reg);

	if (ret < 0) {
		dev_err(&chip->client->dev, "failed reading register\n");
		return ret;
	}

	*val = (uint8_t)ret;
	return 0;
}

/*
 * Write one byte to the given register address.
 */
static int pca9956b_write_reg(struct pca9956b_chip *chip, int reg, uint8_t val)
{
	int ret = i2c_smbus_write_byte_data(chip->client, reg, val);

	if (ret < 0) {
		dev_err(&chip->client->dev, "failed writing register\n");
		return ret;
	}

	return 0;
}

/*
 * Read string from device tree property and write it to the register.
 */
static int pca9956b_readWrite_reg(struct pca9956b_chip *chip,
				char *readStr, int writeRegAddr)
{
	struct device_node *np = chip->client->dev.of_node;
	uint32_t reg_value;
	int ret;

	ret = of_property_read_u32(np, readStr, &reg_value);
	if (ret < 0) {
		dev_err(&chip->client->dev,
			"[%s]: Unable to read %s\n",
			__func__, readStr);
		return ret;
	}

	mutex_lock(&chip->lock);

	ret = pca9956b_write_reg(chip, writeRegAddr, (uint8_t)reg_value);
	if (ret < 0) {
		dev_err(&chip->client->dev,
			"[%s]: Unable to write %s , value = 0x%x\n",
			__func__, readStr, reg_value);
		return ret;
	}

	mutex_unlock(&chip->lock);

	return ret;
}

/*
 * Store one byte to given register address.
 */
static ssize_t pca9956b_storeReg(struct device *dev,
		struct device_attribute *devattr,
		const char *buf, size_t count)
{
	struct pca9956b_chip *chip = dev_get_drvdata(dev);
	unsigned int ret, reg_value, reg_addr;

	ret = sscanf(buf, "%x %x", &reg_addr, &reg_value);
	if (ret == 0) {
		dev_err(&chip->client->dev,
			"[%s] fail to pca9956b out.\n",
			__func__);
		return count;
	}

	if (reg_addr < PCA9956B_MODE1 || reg_addr > PCA9956B_IREFALL) {
		dev_err(&chip->client->dev,
			"[%s] Out of range. Reg = 0x%x\n",
			__func__, reg_addr);
		return count;
	}

	mutex_lock(&chip->lock);

	ret = pca9956b_write_reg(chip, reg_addr, (uint8_t)reg_value);
	if (ret != 0)
		dev_err(&chip->client->dev,
			"[%s] Operation [0x%x , %d] is failed.\n",
			__func__, reg_addr, reg_value);

	mutex_unlock(&chip->lock);

	return count;
}

/*
 * Show all registers
 */
static ssize_t pca9956b_showReg(struct device *dev,
		struct device_attribute *devattr, char *buf)
{
	struct pca9956b_chip *chip = dev_get_drvdata(dev);
	uint8_t reg_value = 0;
	int ret, i;
	char *bufp = buf;

	mutex_lock(&chip->lock);

	for (i = PCA9956B_MODE1; i < PCA9956B_IREFALL; i++) {
		ret = pca9956b_read_reg(chip, i, &reg_value);
		if (ret != 0)
			dev_err(&chip->client->dev,
				"[%s] Reading reg[0x%x] is failed.\n",
				__func__, i);

		bufp += snprintf(bufp, PAGE_SIZE, "Addr[0x%x] = 0x%x\n", i, reg_value);
	}

	mutex_unlock(&chip->lock);

	return strlen(buf);
}

static DEVICE_ATTR(reg, 0664,
		pca9956b_showReg, pca9956b_storeReg);

/*
 * Show error register.
 */
static ssize_t pca9956_showErr(struct device *dev,
		struct device_attribute *devattr, char *buf)
{
	struct pca9956b_chip *chip = dev_get_drvdata(dev);
	uint8_t reg_value = 0;
	int ret, i;
	char *bufp = buf;

	mutex_lock(&chip->lock);

	for (i = PCA9956B_EFLAG0 ; i <= PCA9956B_EFLAG4 ; i++) {
		ret = pca9956b_read_reg(chip, i, &reg_value);
		if (ret != 0)
			dev_err(&chip->client->dev,
				"[%s] Reading [0x%x] is failed.\n",
				__func__, i);

		bufp += snprintf(bufp, PAGE_SIZE, "PCA9956B_EFLAG[%d] = 0x%x\n",
				i - PCA9956B_EFLAG0, reg_value);
	}

	mutex_unlock(&chip->lock);

	return strlen(buf);
}
static DEVICE_ATTR(err, 0664,
				pca9956_showErr, NULL);

static struct attribute *attrs[] = {
	&dev_attr_err.attr,
	&dev_attr_reg.attr,
	NULL, /* Need to NULL terminate the list of attributes */
};

static struct attribute_group attr_group = {
	.attrs = attrs,
};

/*
 * Individual PWM set function
 */
static void pca9956b_brightness_set(struct led_classdev *led_cdev,
				enum led_brightness value)
{
	struct pca9956b_led *pca9956b;
	struct pca9956b_chip *chip;
	int ret;

	pca9956b = container_of(led_cdev, struct pca9956b_led, led_cdev);
	chip = pca9956b->chip;

	mutex_lock(&chip->lock);
	ret = pca9956b_write_reg(chip,
				PCA9956B_PWM0 + pca9956b->led_num,
				value);
	if (ret != 0)
		dev_err(&chip->client->dev, "[%s] is failed = %d.\n",
			__func__, ret);

	mutex_unlock(&chip->lock);
}

/*
 * Individual PWM get function
 */
static enum led_brightness pca9956b_brightness_get(
			struct led_classdev *led_cdev)
{
	struct pca9956b_led *pca9956b;
	struct pca9956b_chip *chip;
	int ret;
	uint8_t reg_value;

	pca9956b = container_of(led_cdev, struct pca9956b_led, led_cdev);
	chip = pca9956b->chip;

	mutex_lock(&chip->lock);
	ret = pca9956b_read_reg(chip, PCA9956B_PWM0 + pca9956b->led_num,
				&reg_value);
	if (ret != 0)
		dev_err(&chip->client->dev, "[%s] is failed = %d.\n",
			__func__, ret);

	mutex_unlock(&chip->lock);

	return reg_value;
}

static int pca9956b_registerClassDevice(struct i2c_client *client,
					struct pca9956b_chip *chip)
{
	int i, err;
	struct pca9956b_led *led;
	struct led_platform_data *pdata;

	pdata = dev_get_platdata(&client->dev);
	if (pdata) {
		if (pdata->num_leds != PCA9956B_LED_NUM) {
			dev_err(&client->dev,
				"board info claims %d LEDs on a %d-bit chip\n",
				pdata->num_leds, PCA9956B_LED_NUM);
			return -ENODEV;
		}
	}

	for (i = 0 ; i < PCA9956B_LED_NUM ; i++) {
		led = &chip->leds[i];
		led->led_num = i;

		/* Platform data can specify LED names and default triggers */
		if (pdata) {
			if (pdata->leds[i].name)
				snprintf(led->name,
					sizeof(led->name), "pca9956b:%s",
					pdata->leds[i].name);
			if (pdata->leds[i].default_trigger)
				led->led_cdev.default_trigger =
					pdata->leds[i].default_trigger;
		} else {
			snprintf(led->name, sizeof(led->name),
				 "pca9956b:%d", i);
		}

		led->led_cdev.name = led->name;
		led->led_cdev.brightness_set = pca9956b_brightness_set;
		led->led_cdev.brightness_get = pca9956b_brightness_get;
		led->chip = chip;
		led->led_num = i;

		err = led_classdev_register(&client->dev,
					&led->led_cdev);
		if (err < 0)
			goto exit;

	}
	return 0;
exit:
	while (i--)
		led_classdev_unregister(&chip->leds[i].led_cdev);

	return err;
}

/*
 * Read properties and write it to register.
 */
static int pca9956b_setup(struct pca9956b_chip *chip)
{
	struct device_node *np = chip->client->dev.of_node;
	int ret;
	uint32_t reg_value;

	ret = of_property_read_u32(np, "pca9956b,support_initialize",
					&reg_value);
	if (ret < 0) {
		pr_err("[%s]: "
			"Unable to pca9956b,support_initialize\n",
			__func__);
		return ret;
	}

	if (0 == reg_value)
		return ret;

	ret = pca9956b_readWrite_reg(chip, "pca9956b,mode1",
					PCA9956B_MODE1);
	if (ret < 0)
		return ret;

	ret = pca9956b_readWrite_reg(chip, "pca9956b,mode2",
					PCA9956B_MODE2);
	if (ret < 0)
		return ret;

	ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout0",
					PCA9956B_LEDOUT0);
	if (ret < 0)
		return ret;

	ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout1",
					PCA9956B_LEDOUT1);
	if (ret < 0)
		return ret;

	ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout2",
					PCA9956B_LEDOUT2);
	if (ret < 0)
		return ret;

	ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout3",
					PCA9956B_LEDOUT3);
	if (ret < 0)
		return ret;

	ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout4",
					PCA9956B_LEDOUT4);
	if (ret < 0)
		return ret;

	ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout5",
					PCA9956B_LEDOUT5);
	if (ret < 0)
		return ret;

	/* set default IREF to all IREF */
	{
		int reg_addr;

		ret = of_property_read_u32(np, "pca9956b,defaultiref",
					&reg_value);
		if (ret < 0) {
			dev_err(&chip->client->dev,
				"[%s]: Unable to read pca9956b,defaultiref\n",
				__func__);
			return ret;
		}
		mutex_lock(&chip->lock);

		for (reg_addr = PCA9956B_IREF0; reg_addr <= PCA9956B_IREF23; reg_addr++) {
			ret = pca9956b_write_reg(chip, reg_addr, (uint8_t)reg_value);
			if (ret < 0) {
				dev_err(&chip->client->dev,
					"[%s]: Unable to write reg0x%x[0x%x]\n",
					__func__, reg_addr, reg_value);
				mutex_unlock(&chip->lock);
				return ret;
			}
		}
		mutex_unlock(&chip->lock);
	}

	/* set IREF0 ~ IREF23 if required */

	return ret;
}


static int pca9956b_probe(struct i2c_client *client,
					const struct i2c_device_id *id)
{
	struct pca9956b_chip *chip;
	struct pca9956b_led *led;
	int ret;

	pr_info(DRIVER_NAME": "" (I2C) "DRIVER_VERSION"\n");

	if (!i2c_check_functionality(client->adapter,
				I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
		return -EIO;
	}

	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
	if (!chip)
		return -ENOMEM;

	chip->leds = devm_kzalloc(&client->dev, sizeof(*led)*PCA9956B_LED_NUM,
				GFP_KERNEL);
	if (!chip->leds) {
		devm_kfree(&client->dev, chip);
		return -ENOMEM;
	}

	i2c_set_clientdata(client, chip);

	mutex_init(&chip->lock);
	chip->client = client;

	/* LED device class registration */
	ret = pca9956b_registerClassDevice(client, chip);
	if (ret < 0)
		goto exit;

	/* Configuration setup */
	ret = pca9956b_setup(chip);
	if (ret < 0)
		goto exit;

	pca9956b_dev = &client->dev;

	ret = sysfs_create_group(&pca9956b_dev->kobj, &attr_group);
	if (ret) {
		dev_err(&client->dev,
				"Failed to create sysfs group for pca9956b\n");
		goto err_sysfs;
	}

	return 0;

err_sysfs:
	class_unregister(nxp_ledseg_class);
	class_destroy(nxp_ledseg_class);
exit:
	devm_kfree(&client->dev, chip);
	devm_kfree(&client->dev, chip->leds);
	return ret;
}

static int pca9956b_remove(struct i2c_client *client)
{
	struct pca9956b_chip *dev = i2c_get_clientdata(client);
	int i;

	for (i = 0; i < PCA9956B_LED_NUM; i++)
		led_classdev_unregister(&dev->leds[i].led_cdev);

	sysfs_remove_group(&pca9956b_dev->kobj, &attr_group);
	class_unregister(nxp_ledseg_class);
	class_destroy(nxp_ledseg_class);

	mutex_destroy(&dev->lock);
	devm_kfree(&client->dev, dev);
	return 0;
}

static struct of_device_id pca9956b_dt_ids[] = {
	{ .compatible = "nxp,pca9956b",},
};

static const struct i2c_device_id pca9956b_id[] = {
	{DRIVER_NAME"-i2c", 0, },
	{ }
};
MODULE_DEVICE_TABLE(i2c, pca9956b_id);

static struct i2c_driver pca9956b_driver = {
	.driver = {
		   .name = DRIVER_NAME"-i2c",
		   .of_match_table = of_match_ptr(pca9956b_dt_ids),
	},
	.probe = pca9956b_probe,
	.remove = pca9956b_remove,
	.id_table = pca9956b_id,
};

static int __init pca9956b_init(void)
{
	return i2c_add_driver(&pca9956b_driver);
}

static void __exit pca9956b_exit(void)
{
	i2c_del_driver(&pca9956b_driver);
}
module_init(pca9956b_init);
module_exit(pca9956b_exit);
MODULE_AUTHOR("NXP Semiconductors");
MODULE_DESCRIPTION("PCA9956B : 24-channel constant current LED driver");
MODULE_LICENSE("GPL v2");
+111 −0
Original line number Diff line number Diff line
/*
 * leds-pca9956b.h - NXP PCA9956B LED segment driver
 *
 * Copyright (C) 2017 NXP Semiconductors
 *
 * 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.
 */

#ifndef PCA9956B_H

/* Register address */
enum {
	PCA9956B_MODE1 = 0x00,  /* AIF, SLEEP, SUBn, ALLCALL */
	PCA9956B_MODE2,         /* OVERTEMP, ERROR, DMBLNK, CLRERR, OCH */
	PCA9956B_LEDOUT0,       /* LED driver output state */
	PCA9956B_LEDOUT1,
	PCA9956B_LEDOUT2,
	PCA9956B_LEDOUT3,
	PCA9956B_LEDOUT4,
	PCA9956B_LEDOUT5,
	PCA9956B_GRPPWM,        /* DMBLINK set 0, then GRPPWM controls
				global brightness */
	PCA9956B_GRPFREQ,       /* DMBLINK set 1, then GRPFREQ controls
				global blinking period */

	/* 10 : 0x0A */
	PCA9956B_PWM0,          /* Brightness control */
	PCA9956B_PWM1,
	PCA9956B_PWM2,
	PCA9956B_PWM3,
	PCA9956B_PWM4,
	PCA9956B_PWM5,
	PCA9956B_PWM6,
	PCA9956B_PWM7,
	PCA9956B_PWM8,
	PCA9956B_PWM9,

	/* 20 : 0x14 */
	PCA9956B_PWM10,
	PCA9956B_PWM11,
	PCA9956B_PWM12,
	PCA9956B_PWM13,
	PCA9956B_PWM14,
	PCA9956B_PWM15,
	PCA9956B_PWM16,
	PCA9956B_PWM17,
	PCA9956B_PWM18,
	PCA9956B_PWM19,

	/* 30 : 0x1E */
	PCA9956B_PWM20,
	PCA9956B_PWM21,
	PCA9956B_PWM22,
	PCA9956B_PWM23,
	PCA9956B_IREF0,         /* Output current control */
	PCA9956B_IREF1,
	PCA9956B_IREF2,
	PCA9956B_IREF3,
	PCA9956B_IREF4,
	PCA9956B_IREF5,

	/* 40 : 0x28 */
	PCA9956B_IREF6,
	PCA9956B_IREF7,
	PCA9956B_IREF8,
	PCA9956B_IREF9,
	PCA9956B_IREF10,
	PCA9956B_IREF11,
	PCA9956B_IREF12,
	PCA9956B_IREF13,
	PCA9956B_IREF14,
	PCA9956B_IREF15,

	/* 50 : 0x32 */
	PCA9956B_IREF16,
	PCA9956B_IREF17,
	PCA9956B_IREF18,
	PCA9956B_IREF19,
	PCA9956B_IREF20,
	PCA9956B_IREF21,
	PCA9956B_IREF22,
	PCA9956B_IREF23,
	PCA9956B_OFFSET,        /* led turn-on delay */
	PCA9956B_SUBADR1,       /* I2C bus subaddress */

	/* 60 : 0x3C */
	PCA9956B_SUBADR2,
	PCA9956B_SUBADR3,
	PCA9956B_ALLCALLADR,    /* Allows all the PCA9956Bs on the bus to be
				programmed at the same time */
	PCA9956B_PWMALL,        /* brightness control for all LEDn outputs */
	PCA9956B_IREFALL,       /* output current value for all LED outputs */
	PCA9956B_EFLAG0,        /* LED error detection */
	PCA9956B_EFLAG1,
	PCA9956B_EFLAG2,
	PCA9956B_EFLAG3,
	PCA9956B_EFLAG4,

	/* 70 : 0x46 */
	PCA9956B_EFLAG5,
};

#endif /* PCA9956B_H */