brintos

brintos / linux-shallow public Read only

0
0
Text · 13.0 KiB · 8793330 Raw
494 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Driver for ISSI IS31FL32xx family of I2C LED controllers4 *5 * Copyright 2015 Allworx Corp.6 *7 * Datasheets:8 *   http://www.issi.com/US/product-analog-fxled-driver.shtml9 *   http://www.si-en.com/product.asp?parentid=89010 */11 12#include <linux/device.h>13#include <linux/i2c.h>14#include <linux/kernel.h>15#include <linux/leds.h>16#include <linux/module.h>17#include <linux/of.h>18 19/* Used to indicate a device has no such register */20#define IS31FL32XX_REG_NONE 0xFF21 22/* Software Shutdown bit in Shutdown Register */23#define IS31FL32XX_SHUTDOWN_SSD_ENABLE  024#define IS31FL32XX_SHUTDOWN_SSD_DISABLE BIT(0)25 26/* IS31FL3216 has a number of unique registers */27#define IS31FL3216_CONFIG_REG 0x0028#define IS31FL3216_LIGHTING_EFFECT_REG 0x0329#define IS31FL3216_CHANNEL_CONFIG_REG 0x0430 31/* Software Shutdown bit in 3216 Config Register */32#define IS31FL3216_CONFIG_SSD_ENABLE  BIT(7)33#define IS31FL3216_CONFIG_SSD_DISABLE 034 35struct is31fl32xx_priv;36struct is31fl32xx_led_data {37	struct led_classdev cdev;38	u8 channel; /* 1-based, max priv->cdef->channels */39	struct is31fl32xx_priv *priv;40};41 42struct is31fl32xx_priv {43	const struct is31fl32xx_chipdef *cdef;44	struct i2c_client *client;45	unsigned int num_leds;46	struct is31fl32xx_led_data leds[];47};48 49/**50 * struct is31fl32xx_chipdef - chip-specific attributes51 * @channels            : Number of LED channels52 * @shutdown_reg        : address of Shutdown register (optional)53 * @pwm_update_reg      : address of PWM Update register54 * @global_control_reg  : address of Global Control register (optional)55 * @reset_reg           : address of Reset register (optional)56 * @pwm_register_base   : address of first PWM register57 * @pwm_registers_reversed: : true if PWM registers count down instead of up58 * @led_control_register_base : address of first LED control register (optional)59 * @enable_bits_per_led_control_register: number of LEDs enable bits in each60 * @reset_func          : pointer to reset function61 * @sw_shutdown_func    : pointer to software shutdown function62 *63 * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE64 * indicates that this chip has no such register.65 *66 * If non-NULL, @reset_func will be called during probing to set all67 * necessary registers to a known initialization state. This is needed68 * for chips that do not have a @reset_reg.69 *70 * @enable_bits_per_led_control_register must be >=1 if71 * @led_control_register_base != %IS31FL32XX_REG_NONE.72 */73struct is31fl32xx_chipdef {74	u8	channels;75	u8	shutdown_reg;76	u8	pwm_update_reg;77	u8	global_control_reg;78	u8	reset_reg;79	u8	pwm_register_base;80	bool	pwm_registers_reversed;81	u8	led_control_register_base;82	u8	enable_bits_per_led_control_register;83	int (*reset_func)(struct is31fl32xx_priv *priv);84	int (*sw_shutdown_func)(struct is31fl32xx_priv *priv, bool enable);85};86 87static const struct is31fl32xx_chipdef is31fl3236_cdef = {88	.channels				= 36,89	.shutdown_reg				= 0x00,90	.pwm_update_reg				= 0x25,91	.global_control_reg			= 0x4a,92	.reset_reg				= 0x4f,93	.pwm_register_base			= 0x01,94	.led_control_register_base		= 0x26,95	.enable_bits_per_led_control_register	= 1,96};97 98static const struct is31fl32xx_chipdef is31fl3235_cdef = {99	.channels				= 28,100	.shutdown_reg				= 0x00,101	.pwm_update_reg				= 0x25,102	.global_control_reg			= 0x4a,103	.reset_reg				= 0x4f,104	.pwm_register_base			= 0x05,105	.led_control_register_base		= 0x2a,106	.enable_bits_per_led_control_register	= 1,107};108 109static const struct is31fl32xx_chipdef is31fl3218_cdef = {110	.channels				= 18,111	.shutdown_reg				= 0x00,112	.pwm_update_reg				= 0x16,113	.global_control_reg			= IS31FL32XX_REG_NONE,114	.reset_reg				= 0x17,115	.pwm_register_base			= 0x01,116	.led_control_register_base		= 0x13,117	.enable_bits_per_led_control_register	= 6,118};119 120static int is31fl3216_reset(struct is31fl32xx_priv *priv);121static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv,122					bool enable);123static const struct is31fl32xx_chipdef is31fl3216_cdef = {124	.channels				= 16,125	.shutdown_reg				= IS31FL32XX_REG_NONE,126	.pwm_update_reg				= 0xB0,127	.global_control_reg			= IS31FL32XX_REG_NONE,128	.reset_reg				= IS31FL32XX_REG_NONE,129	.pwm_register_base			= 0x10,130	.pwm_registers_reversed			= true,131	.led_control_register_base		= 0x01,132	.enable_bits_per_led_control_register	= 8,133	.reset_func				= is31fl3216_reset,134	.sw_shutdown_func			= is31fl3216_software_shutdown,135};136 137static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)138{139	int ret;140 141	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);142 143	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);144	if (ret) {145		dev_err(&priv->client->dev,146			"register write to 0x%02X failed (error %d)",147			reg, ret);148	}149	return ret;150}151 152/*153 * Custom reset function for IS31FL3216 because it does not have a RESET154 * register the way that the other IS31FL32xx chips do. We don't bother155 * writing the GPIO and animation registers, because the registers we156 * do write ensure those will have no effect.157 */158static int is31fl3216_reset(struct is31fl32xx_priv *priv)159{160	unsigned int i;161	int ret;162 163	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG,164			       IS31FL3216_CONFIG_SSD_ENABLE);165	if (ret)166		return ret;167	for (i = 0; i < priv->cdef->channels; i++) {168		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,169				       0x00);170		if (ret)171			return ret;172	}173	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);174	if (ret)175		return ret;176	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);177	if (ret)178		return ret;179	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);180	if (ret)181		return ret;182 183	return 0;184}185 186/*187 * Custom Software-Shutdown function for IS31FL3216 because it does not have188 * a SHUTDOWN register the way that the other IS31FL32xx chips do.189 * We don't bother doing a read/modify/write on the CONFIG register because190 * we only ever use a value of '0' for the other fields in that register.191 */192static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv,193					bool enable)194{195	u8 value = enable ? IS31FL3216_CONFIG_SSD_ENABLE :196			    IS31FL3216_CONFIG_SSD_DISABLE;197 198	return is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, value);199}200 201/*202 * NOTE: A mutex is not needed in this function because:203 * - All referenced data is read-only after probe()204 * - The I2C core has a mutex on to protect the bus205 * - There are no read/modify/write operations206 * - Intervening operations between the write of the PWM register207 *   and the Update register are harmless.208 *209 * Example:210 *	PWM_REG_1 write 16211 *	UPDATE_REG write 0212 *	PWM_REG_2 write 128213 *	UPDATE_REG write 0214 *   vs:215 *	PWM_REG_1 write 16216 *	PWM_REG_2 write 128217 *	UPDATE_REG write 0218 *	UPDATE_REG write 0219 * are equivalent. Poking the Update register merely applies all PWM220 * register writes up to that point.221 */222static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,223				     enum led_brightness brightness)224{225	const struct is31fl32xx_led_data *led_data =226		container_of(led_cdev, struct is31fl32xx_led_data, cdev);227	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;228	u8 pwm_register_offset;229	int ret;230 231	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);232 233	/* NOTE: led_data->channel is 1-based */234	if (cdef->pwm_registers_reversed)235		pwm_register_offset = cdef->channels - led_data->channel;236	else237		pwm_register_offset = led_data->channel - 1;238 239	ret = is31fl32xx_write(led_data->priv,240			       cdef->pwm_register_base + pwm_register_offset,241			       brightness);242	if (ret)243		return ret;244 245	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);246}247 248static int is31fl32xx_reset_regs(struct is31fl32xx_priv *priv)249{250	const struct is31fl32xx_chipdef *cdef = priv->cdef;251	int ret;252 253	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {254		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);255		if (ret)256			return ret;257	}258 259	if (cdef->reset_func)260		return cdef->reset_func(priv);261 262	return 0;263}264 265static int is31fl32xx_software_shutdown(struct is31fl32xx_priv *priv,266					bool enable)267{268	const struct is31fl32xx_chipdef *cdef = priv->cdef;269	int ret;270 271	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {272		u8 value = enable ? IS31FL32XX_SHUTDOWN_SSD_ENABLE :273				    IS31FL32XX_SHUTDOWN_SSD_DISABLE;274		ret = is31fl32xx_write(priv, cdef->shutdown_reg, value);275		if (ret)276			return ret;277	}278 279	if (cdef->sw_shutdown_func)280		return cdef->sw_shutdown_func(priv, enable);281 282	return 0;283}284 285static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)286{287	const struct is31fl32xx_chipdef *cdef = priv->cdef;288	int ret;289 290	ret = is31fl32xx_reset_regs(priv);291	if (ret)292		return ret;293 294	/*295	 * Set enable bit for all channels.296	 * We will control state with PWM registers alone.297	 */298	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {299		u8 value =300		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);301		u8 num_regs = cdef->channels /302				cdef->enable_bits_per_led_control_register;303		int i;304 305		for (i = 0; i < num_regs; i++) {306			ret = is31fl32xx_write(priv,307					       cdef->led_control_register_base+i,308					       value);309			if (ret)310				return ret;311		}312	}313 314	ret = is31fl32xx_software_shutdown(priv, false);315	if (ret)316		return ret;317 318	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {319		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);320		if (ret)321			return ret;322	}323 324	return 0;325}326 327static int is31fl32xx_parse_child_dt(const struct device *dev,328				     const struct device_node *child,329				     struct is31fl32xx_led_data *led_data)330{331	struct led_classdev *cdev = &led_data->cdev;332	int ret = 0;333	u32 reg;334 335	ret = of_property_read_u32(child, "reg", &reg);336	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {337		dev_err(dev,338			"Child node %pOF does not have a valid reg property\n",339			child);340		return -EINVAL;341	}342	led_data->channel = reg;343 344	cdev->brightness_set_blocking = is31fl32xx_brightness_set;345 346	return 0;347}348 349static struct is31fl32xx_led_data *is31fl32xx_find_led_data(350					struct is31fl32xx_priv *priv,351					u8 channel)352{353	size_t i;354 355	for (i = 0; i < priv->num_leds; i++) {356		if (priv->leds[i].channel == channel)357			return &priv->leds[i];358	}359 360	return NULL;361}362 363static int is31fl32xx_parse_dt(struct device *dev,364			       struct is31fl32xx_priv *priv)365{366	int ret = 0;367 368	for_each_available_child_of_node_scoped(dev_of_node(dev), child) {369		struct led_init_data init_data = {};370		struct is31fl32xx_led_data *led_data =371			&priv->leds[priv->num_leds];372		const struct is31fl32xx_led_data *other_led_data;373 374		led_data->priv = priv;375 376		ret = is31fl32xx_parse_child_dt(dev, child, led_data);377		if (ret)378			return ret;379 380		/* Detect if channel is already in use by another child */381		other_led_data = is31fl32xx_find_led_data(priv,382							  led_data->channel);383		if (other_led_data) {384			dev_err(dev,385				"Node %pOF 'reg' conflicts with another LED\n",386				child);387			return -EINVAL;388		}389 390		init_data.fwnode = of_fwnode_handle(child);391 392		ret = devm_led_classdev_register_ext(dev, &led_data->cdev,393						     &init_data);394		if (ret) {395			dev_err(dev, "Failed to register LED for %pOF: %d\n",396				child, ret);397			return ret;398		}399 400		priv->num_leds++;401	}402 403	return 0;404}405 406static const struct of_device_id of_is31fl32xx_match[] = {407	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },408	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },409	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },410	{ .compatible = "si-en,sn3218",    .data = &is31fl3218_cdef, },411	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },412	{ .compatible = "si-en,sn3216",    .data = &is31fl3216_cdef, },413	{},414};415 416MODULE_DEVICE_TABLE(of, of_is31fl32xx_match);417 418static int is31fl32xx_probe(struct i2c_client *client)419{420	const struct is31fl32xx_chipdef *cdef;421	struct device *dev = &client->dev;422	struct is31fl32xx_priv *priv;423	int count;424	int ret = 0;425 426	cdef = device_get_match_data(dev);427 428	count = of_get_available_child_count(dev_of_node(dev));429	if (!count)430		return -EINVAL;431 432	priv = devm_kzalloc(dev, struct_size(priv, leds, count),433			    GFP_KERNEL);434	if (!priv)435		return -ENOMEM;436 437	priv->client = client;438	priv->cdef = cdef;439	i2c_set_clientdata(client, priv);440 441	ret = is31fl32xx_init_regs(priv);442	if (ret)443		return ret;444 445	ret = is31fl32xx_parse_dt(dev, priv);446	if (ret)447		return ret;448 449	return 0;450}451 452static void is31fl32xx_remove(struct i2c_client *client)453{454	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);455	int ret;456 457	ret = is31fl32xx_reset_regs(priv);458	if (ret)459		dev_err(&client->dev, "Failed to reset registers on removal (%pe)\n",460			ERR_PTR(ret));461}462 463/*464 * i2c-core (and modalias) requires that id_table be properly filled,465 * even though it is not used for DeviceTree based instantiation.466 */467static const struct i2c_device_id is31fl32xx_id[] = {468	{ "is31fl3236" },469	{ "is31fl3235" },470	{ "is31fl3218" },471	{ "sn3218" },472	{ "is31fl3216" },473	{ "sn3216" },474	{},475};476 477MODULE_DEVICE_TABLE(i2c, is31fl32xx_id);478 479static struct i2c_driver is31fl32xx_driver = {480	.driver = {481		.name	= "is31fl32xx",482		.of_match_table = of_is31fl32xx_match,483	},484	.probe		= is31fl32xx_probe,485	.remove		= is31fl32xx_remove,486	.id_table	= is31fl32xx_id,487};488 489module_i2c_driver(is31fl32xx_driver);490 491MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");492MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");493MODULE_LICENSE("GPL v2");494