Linux Audio

Check our new training course

Loading...
v6.2
  1// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
  2//
  3// Copyright (c) 2018 Mellanox Technologies. All rights reserved.
  4// Copyright (c) 2018 Vadim Pasternak <vadimp@mellanox.com>
  5
  6#include <linux/bitops.h>
  7#include <linux/device.h>
  8#include <linux/io.h>
  9#include <linux/leds.h>
 10#include <linux/module.h>
 11#include <linux/of_device.h>
 12#include <linux/platform_data/mlxreg.h>
 13#include <linux/platform_device.h>
 14#include <linux/regmap.h>
 15
 16/* Codes for LEDs. */
 17#define MLXREG_LED_OFFSET_BLINK_3HZ	0x01 /* Offset from solid: 3Hz blink */
 18#define MLXREG_LED_OFFSET_BLINK_6HZ	0x02 /* Offset from solid: 6Hz blink */
 19#define MLXREG_LED_IS_OFF		0x00 /* Off */
 20#define MLXREG_LED_RED_SOLID		0x05 /* Solid red */
 21#define MLXREG_LED_GREEN_SOLID		0x0D /* Solid green */
 22#define MLXREG_LED_AMBER_SOLID		0x09 /* Solid amber */
 23#define MLXREG_LED_BLINK_3HZ		167 /* ~167 msec off/on - HW support */
 24#define MLXREG_LED_BLINK_6HZ		83 /* ~83 msec off/on - HW support */
 25#define MLXREG_LED_CAPABILITY_CLEAR	GENMASK(31, 8) /* Clear mask */
 26
 27/**
 28 * struct mlxreg_led_data - led control data:
 29 *
 30 * @data: led configuration data;
 31 * @led_cdev: led class data;
 32 * @base_color: base led color (other colors have constant offset from base);
 33 * @led_data: led data;
 34 * @data_parent: pointer to private device control data of parent;
 35 * @led_cdev_name: class device name
 36 */
 37struct mlxreg_led_data {
 38	struct mlxreg_core_data *data;
 39	struct led_classdev led_cdev;
 40	u8 base_color;
 41	void *data_parent;
 42	char led_cdev_name[MLXREG_CORE_LABEL_MAX_SIZE];
 43};
 44
 45#define cdev_to_priv(c) container_of(c, struct mlxreg_led_data, led_cdev)
 46
 47/**
 48 * struct mlxreg_led_priv_data - platform private data:
 49 *
 50 * @pdev: platform device;
 51 * @pdata: platform data;
 52 * @access_lock: mutex for attribute IO access;
 53 */
 54struct mlxreg_led_priv_data {
 55	struct platform_device *pdev;
 56	struct mlxreg_core_platform_data *pdata;
 57	struct mutex access_lock; /* protect IO operations */
 58};
 59
 60static int
 61mlxreg_led_store_hw(struct mlxreg_led_data *led_data, u8 vset)
 62{
 63	struct mlxreg_led_priv_data *priv = led_data->data_parent;
 64	struct mlxreg_core_platform_data *led_pdata = priv->pdata;
 65	struct mlxreg_core_data *data = led_data->data;
 66	u32 regval;
 67	u32 nib;
 68	int ret;
 69
 70	/*
 71	 * Each LED is controlled through low or high nibble of the relevant
 72	 * register byte. Register offset is specified by off parameter.
 73	 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
 74	 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
 75	 * green.
 76	 * Parameter mask specifies which nibble is used for specific LED: mask
 77	 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
 78	 * higher nibble (bits from 4 to 7).
 79	 */
 80	mutex_lock(&priv->access_lock);
 81
 82	ret = regmap_read(led_pdata->regmap, data->reg, &regval);
 83	if (ret)
 84		goto access_error;
 85
 86	nib = (ror32(data->mask, data->bit) == 0xf0) ? rol32(vset, data->bit) :
 87	      rol32(vset, data->bit + 4);
 88	regval = (regval & data->mask) | nib;
 89
 90	ret = regmap_write(led_pdata->regmap, data->reg, regval);
 91
 92access_error:
 93	mutex_unlock(&priv->access_lock);
 94
 95	return ret;
 96}
 97
 98static enum led_brightness
 99mlxreg_led_get_hw(struct mlxreg_led_data *led_data)
100{
101	struct mlxreg_led_priv_data *priv = led_data->data_parent;
102	struct mlxreg_core_platform_data *led_pdata = priv->pdata;
103	struct mlxreg_core_data *data = led_data->data;
104	u32 regval;
105	int err;
106
107	/*
108	 * Each LED is controlled through low or high nibble of the relevant
109	 * register byte. Register offset is specified by off parameter.
110	 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
111	 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
112	 * green.
113	 * Parameter mask specifies which nibble is used for specific LED: mask
114	 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
115	 * higher nibble (bits from 4 to 7).
116	 */
117	err = regmap_read(led_pdata->regmap, data->reg, &regval);
118	if (err < 0) {
119		dev_warn(led_data->led_cdev.dev, "Failed to get current brightness, error: %d\n",
120			 err);
121		/* Assume the LED is OFF */
122		return LED_OFF;
123	}
124
125	regval = regval & ~data->mask;
126	regval = (ror32(data->mask, data->bit) == 0xf0) ? ror32(regval,
127		 data->bit) : ror32(regval, data->bit + 4);
128	if (regval >= led_data->base_color &&
129	    regval <= (led_data->base_color + MLXREG_LED_OFFSET_BLINK_6HZ))
130		return LED_FULL;
131
132	return LED_OFF;
133}
134
135static int
136mlxreg_led_brightness_set(struct led_classdev *cled, enum led_brightness value)
137{
138	struct mlxreg_led_data *led_data = cdev_to_priv(cled);
139
140	if (value)
141		return mlxreg_led_store_hw(led_data, led_data->base_color);
142	else
143		return mlxreg_led_store_hw(led_data, MLXREG_LED_IS_OFF);
144}
145
146static enum led_brightness
147mlxreg_led_brightness_get(struct led_classdev *cled)
148{
149	struct mlxreg_led_data *led_data = cdev_to_priv(cled);
150
151	return mlxreg_led_get_hw(led_data);
152}
153
154static int
155mlxreg_led_blink_set(struct led_classdev *cled, unsigned long *delay_on,
156		     unsigned long *delay_off)
157{
158	struct mlxreg_led_data *led_data = cdev_to_priv(cled);
159	int err;
160
161	/*
162	 * HW supports two types of blinking: full (6Hz) and half (3Hz).
163	 * For delay on/off zero LED is setting to solid color. For others
164	 * combination blinking is to be controlled by the software timer.
165	 */
166	if (!(*delay_on == 0 && *delay_off == 0) &&
167	    !(*delay_on == MLXREG_LED_BLINK_3HZ &&
168	      *delay_off == MLXREG_LED_BLINK_3HZ) &&
169	    !(*delay_on == MLXREG_LED_BLINK_6HZ &&
170	      *delay_off == MLXREG_LED_BLINK_6HZ))
171		return -EINVAL;
172
173	if (*delay_on == MLXREG_LED_BLINK_6HZ)
174		err = mlxreg_led_store_hw(led_data, led_data->base_color +
175					  MLXREG_LED_OFFSET_BLINK_6HZ);
176	else if (*delay_on == MLXREG_LED_BLINK_3HZ)
177		err = mlxreg_led_store_hw(led_data, led_data->base_color +
178					  MLXREG_LED_OFFSET_BLINK_3HZ);
179	else
180		err = mlxreg_led_store_hw(led_data, led_data->base_color);
181
182	return err;
183}
184
185static int mlxreg_led_config(struct mlxreg_led_priv_data *priv)
186{
187	struct mlxreg_core_platform_data *led_pdata = priv->pdata;
188	struct mlxreg_core_data *data = led_pdata->data;
189	struct mlxreg_led_data *led_data;
190	struct led_classdev *led_cdev;
191	enum led_brightness brightness;
192	u32 regval;
193	int i;
194	int err;
195
196	for (i = 0; i < led_pdata->counter; i++, data++) {
197		led_data = devm_kzalloc(&priv->pdev->dev, sizeof(*led_data),
198					GFP_KERNEL);
199		if (!led_data)
200			return -ENOMEM;
201
202		if (data->capability) {
203			err = regmap_read(led_pdata->regmap, data->capability,
204					  &regval);
205			if (err) {
206				dev_err(&priv->pdev->dev, "Failed to query capability register\n");
207				return err;
208			}
209			if (!(regval & data->bit))
210				continue;
211			/*
212			 * Field "bit" can contain one capability bit in 0 byte
213			 * and offset bit in 1-3 bytes. Clear capability bit and
214			 * keep only offset bit.
215			 */
216			data->bit &= MLXREG_LED_CAPABILITY_CLEAR;
217		}
218
219		led_cdev = &led_data->led_cdev;
220		led_data->data_parent = priv;
221		if (strstr(data->label, "red") ||
222		    strstr(data->label, "orange")) {
223			brightness = LED_OFF;
224			led_data->base_color = MLXREG_LED_RED_SOLID;
225		} else if (strstr(data->label, "amber")) {
226			brightness = LED_OFF;
227			led_data->base_color = MLXREG_LED_AMBER_SOLID;
228		} else {
229			brightness = LED_OFF;
230			led_data->base_color = MLXREG_LED_GREEN_SOLID;
231		}
232		snprintf(led_data->led_cdev_name, sizeof(led_data->led_cdev_name),
233			 "mlxreg:%s", data->label);
234		led_cdev->name = led_data->led_cdev_name;
235		led_cdev->brightness = brightness;
236		led_cdev->max_brightness = LED_ON;
237		led_cdev->brightness_set_blocking =
238						mlxreg_led_brightness_set;
239		led_cdev->brightness_get = mlxreg_led_brightness_get;
240		led_cdev->blink_set = mlxreg_led_blink_set;
241		led_cdev->flags = LED_CORE_SUSPENDRESUME;
242		led_data->data = data;
243		err = devm_led_classdev_register(&priv->pdev->dev, led_cdev);
244		if (err)
245			return err;
246
247		if (led_cdev->brightness)
248			mlxreg_led_brightness_set(led_cdev,
249						  led_cdev->brightness);
250		dev_info(led_cdev->dev, "label: %s, mask: 0x%02x, offset:0x%02x\n",
251			 data->label, data->mask, data->reg);
252	}
253
254	return 0;
255}
256
257static int mlxreg_led_probe(struct platform_device *pdev)
258{
259	struct mlxreg_core_platform_data *led_pdata;
260	struct mlxreg_led_priv_data *priv;
261
262	led_pdata = dev_get_platdata(&pdev->dev);
263	if (!led_pdata) {
264		dev_err(&pdev->dev, "Failed to get platform data.\n");
265		return -EINVAL;
266	}
267
268	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
269	if (!priv)
270		return -ENOMEM;
271
272	mutex_init(&priv->access_lock);
273	priv->pdev = pdev;
274	priv->pdata = led_pdata;
275
276	return mlxreg_led_config(priv);
277}
278
279static int mlxreg_led_remove(struct platform_device *pdev)
280{
281	struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev);
282
283	mutex_destroy(&priv->access_lock);
284
285	return 0;
286}
287
288static struct platform_driver mlxreg_led_driver = {
289	.driver = {
290	    .name = "leds-mlxreg",
291	},
292	.probe = mlxreg_led_probe,
293	.remove = mlxreg_led_remove,
294};
295
296module_platform_driver(mlxreg_led_driver);
297
298MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
299MODULE_DESCRIPTION("Mellanox LED regmap driver");
300MODULE_LICENSE("Dual BSD/GPL");
301MODULE_ALIAS("platform:leds-mlxreg");
v5.14.15
  1// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
  2//
  3// Copyright (c) 2018 Mellanox Technologies. All rights reserved.
  4// Copyright (c) 2018 Vadim Pasternak <vadimp@mellanox.com>
  5
  6#include <linux/bitops.h>
  7#include <linux/device.h>
  8#include <linux/io.h>
  9#include <linux/leds.h>
 10#include <linux/module.h>
 11#include <linux/of_device.h>
 12#include <linux/platform_data/mlxreg.h>
 13#include <linux/platform_device.h>
 14#include <linux/regmap.h>
 15
 16/* Codes for LEDs. */
 17#define MLXREG_LED_OFFSET_BLINK_3HZ	0x01 /* Offset from solid: 3Hz blink */
 18#define MLXREG_LED_OFFSET_BLINK_6HZ	0x02 /* Offset from solid: 6Hz blink */
 19#define MLXREG_LED_IS_OFF		0x00 /* Off */
 20#define MLXREG_LED_RED_SOLID		0x05 /* Solid red */
 21#define MLXREG_LED_GREEN_SOLID		0x0D /* Solid green */
 22#define MLXREG_LED_AMBER_SOLID		0x09 /* Solid amber */
 23#define MLXREG_LED_BLINK_3HZ		167 /* ~167 msec off/on - HW support */
 24#define MLXREG_LED_BLINK_6HZ		83 /* ~83 msec off/on - HW support */
 25#define MLXREG_LED_CAPABILITY_CLEAR	GENMASK(31, 8) /* Clear mask */
 26
 27/**
 28 * struct mlxreg_led_data - led control data:
 29 *
 30 * @data: led configuration data;
 31 * @led_cdev: led class data;
 32 * @base_color: base led color (other colors have constant offset from base);
 33 * @led_data: led data;
 34 * @data_parent: pointer to private device control data of parent;
 35 * @led_cdev_name: class device name
 36 */
 37struct mlxreg_led_data {
 38	struct mlxreg_core_data *data;
 39	struct led_classdev led_cdev;
 40	u8 base_color;
 41	void *data_parent;
 42	char led_cdev_name[MLXREG_CORE_LABEL_MAX_SIZE];
 43};
 44
 45#define cdev_to_priv(c) container_of(c, struct mlxreg_led_data, led_cdev)
 46
 47/**
 48 * struct mlxreg_led_priv_data - platform private data:
 49 *
 50 * @pdev: platform device;
 51 * @pdata: platform data;
 52 * @access_lock: mutex for attribute IO access;
 53 */
 54struct mlxreg_led_priv_data {
 55	struct platform_device *pdev;
 56	struct mlxreg_core_platform_data *pdata;
 57	struct mutex access_lock; /* protect IO operations */
 58};
 59
 60static int
 61mlxreg_led_store_hw(struct mlxreg_led_data *led_data, u8 vset)
 62{
 63	struct mlxreg_led_priv_data *priv = led_data->data_parent;
 64	struct mlxreg_core_platform_data *led_pdata = priv->pdata;
 65	struct mlxreg_core_data *data = led_data->data;
 66	u32 regval;
 67	u32 nib;
 68	int ret;
 69
 70	/*
 71	 * Each LED is controlled through low or high nibble of the relevant
 72	 * register byte. Register offset is specified by off parameter.
 73	 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
 74	 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
 75	 * green.
 76	 * Parameter mask specifies which nibble is used for specific LED: mask
 77	 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
 78	 * higher nibble (bits from 4 to 7).
 79	 */
 80	mutex_lock(&priv->access_lock);
 81
 82	ret = regmap_read(led_pdata->regmap, data->reg, &regval);
 83	if (ret)
 84		goto access_error;
 85
 86	nib = (ror32(data->mask, data->bit) == 0xf0) ? rol32(vset, data->bit) :
 87	      rol32(vset, data->bit + 4);
 88	regval = (regval & data->mask) | nib;
 89
 90	ret = regmap_write(led_pdata->regmap, data->reg, regval);
 91
 92access_error:
 93	mutex_unlock(&priv->access_lock);
 94
 95	return ret;
 96}
 97
 98static enum led_brightness
 99mlxreg_led_get_hw(struct mlxreg_led_data *led_data)
100{
101	struct mlxreg_led_priv_data *priv = led_data->data_parent;
102	struct mlxreg_core_platform_data *led_pdata = priv->pdata;
103	struct mlxreg_core_data *data = led_data->data;
104	u32 regval;
105	int err;
106
107	/*
108	 * Each LED is controlled through low or high nibble of the relevant
109	 * register byte. Register offset is specified by off parameter.
110	 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
111	 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
112	 * green.
113	 * Parameter mask specifies which nibble is used for specific LED: mask
114	 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
115	 * higher nibble (bits from 4 to 7).
116	 */
117	err = regmap_read(led_pdata->regmap, data->reg, &regval);
118	if (err < 0) {
119		dev_warn(led_data->led_cdev.dev, "Failed to get current brightness, error: %d\n",
120			 err);
121		/* Assume the LED is OFF */
122		return LED_OFF;
123	}
124
125	regval = regval & ~data->mask;
126	regval = (ror32(data->mask, data->bit) == 0xf0) ? ror32(regval,
127		 data->bit) : ror32(regval, data->bit + 4);
128	if (regval >= led_data->base_color &&
129	    regval <= (led_data->base_color + MLXREG_LED_OFFSET_BLINK_6HZ))
130		return LED_FULL;
131
132	return LED_OFF;
133}
134
135static int
136mlxreg_led_brightness_set(struct led_classdev *cled, enum led_brightness value)
137{
138	struct mlxreg_led_data *led_data = cdev_to_priv(cled);
139
140	if (value)
141		return mlxreg_led_store_hw(led_data, led_data->base_color);
142	else
143		return mlxreg_led_store_hw(led_data, MLXREG_LED_IS_OFF);
144}
145
146static enum led_brightness
147mlxreg_led_brightness_get(struct led_classdev *cled)
148{
149	struct mlxreg_led_data *led_data = cdev_to_priv(cled);
150
151	return mlxreg_led_get_hw(led_data);
152}
153
154static int
155mlxreg_led_blink_set(struct led_classdev *cled, unsigned long *delay_on,
156		     unsigned long *delay_off)
157{
158	struct mlxreg_led_data *led_data = cdev_to_priv(cled);
159	int err;
160
161	/*
162	 * HW supports two types of blinking: full (6Hz) and half (3Hz).
163	 * For delay on/off zero LED is setting to solid color. For others
164	 * combination blinking is to be controlled by the software timer.
165	 */
166	if (!(*delay_on == 0 && *delay_off == 0) &&
167	    !(*delay_on == MLXREG_LED_BLINK_3HZ &&
168	      *delay_off == MLXREG_LED_BLINK_3HZ) &&
169	    !(*delay_on == MLXREG_LED_BLINK_6HZ &&
170	      *delay_off == MLXREG_LED_BLINK_6HZ))
171		return -EINVAL;
172
173	if (*delay_on == MLXREG_LED_BLINK_6HZ)
174		err = mlxreg_led_store_hw(led_data, led_data->base_color +
175					  MLXREG_LED_OFFSET_BLINK_6HZ);
176	else if (*delay_on == MLXREG_LED_BLINK_3HZ)
177		err = mlxreg_led_store_hw(led_data, led_data->base_color +
178					  MLXREG_LED_OFFSET_BLINK_3HZ);
179	else
180		err = mlxreg_led_store_hw(led_data, led_data->base_color);
181
182	return err;
183}
184
185static int mlxreg_led_config(struct mlxreg_led_priv_data *priv)
186{
187	struct mlxreg_core_platform_data *led_pdata = priv->pdata;
188	struct mlxreg_core_data *data = led_pdata->data;
189	struct mlxreg_led_data *led_data;
190	struct led_classdev *led_cdev;
191	enum led_brightness brightness;
192	u32 regval;
193	int i;
194	int err;
195
196	for (i = 0; i < led_pdata->counter; i++, data++) {
197		led_data = devm_kzalloc(&priv->pdev->dev, sizeof(*led_data),
198					GFP_KERNEL);
199		if (!led_data)
200			return -ENOMEM;
201
202		if (data->capability) {
203			err = regmap_read(led_pdata->regmap, data->capability,
204					  &regval);
205			if (err) {
206				dev_err(&priv->pdev->dev, "Failed to query capability register\n");
207				return err;
208			}
209			if (!(regval & data->bit))
210				continue;
211			/*
212			 * Field "bit" can contain one capability bit in 0 byte
213			 * and offset bit in 1-3 bytes. Clear capability bit and
214			 * keep only offset bit.
215			 */
216			data->bit &= MLXREG_LED_CAPABILITY_CLEAR;
217		}
218
219		led_cdev = &led_data->led_cdev;
220		led_data->data_parent = priv;
221		if (strstr(data->label, "red") ||
222		    strstr(data->label, "orange")) {
223			brightness = LED_OFF;
224			led_data->base_color = MLXREG_LED_RED_SOLID;
225		} else if (strstr(data->label, "amber")) {
226			brightness = LED_OFF;
227			led_data->base_color = MLXREG_LED_AMBER_SOLID;
228		} else {
229			brightness = LED_OFF;
230			led_data->base_color = MLXREG_LED_GREEN_SOLID;
231		}
232		snprintf(led_data->led_cdev_name, sizeof(led_data->led_cdev_name),
233			 "mlxreg:%s", data->label);
234		led_cdev->name = led_data->led_cdev_name;
235		led_cdev->brightness = brightness;
236		led_cdev->max_brightness = LED_ON;
237		led_cdev->brightness_set_blocking =
238						mlxreg_led_brightness_set;
239		led_cdev->brightness_get = mlxreg_led_brightness_get;
240		led_cdev->blink_set = mlxreg_led_blink_set;
241		led_cdev->flags = LED_CORE_SUSPENDRESUME;
242		led_data->data = data;
243		err = devm_led_classdev_register(&priv->pdev->dev, led_cdev);
244		if (err)
245			return err;
246
247		if (led_cdev->brightness)
248			mlxreg_led_brightness_set(led_cdev,
249						  led_cdev->brightness);
250		dev_info(led_cdev->dev, "label: %s, mask: 0x%02x, offset:0x%02x\n",
251			 data->label, data->mask, data->reg);
252	}
253
254	return 0;
255}
256
257static int mlxreg_led_probe(struct platform_device *pdev)
258{
259	struct mlxreg_core_platform_data *led_pdata;
260	struct mlxreg_led_priv_data *priv;
261
262	led_pdata = dev_get_platdata(&pdev->dev);
263	if (!led_pdata) {
264		dev_err(&pdev->dev, "Failed to get platform data.\n");
265		return -EINVAL;
266	}
267
268	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
269	if (!priv)
270		return -ENOMEM;
271
272	mutex_init(&priv->access_lock);
273	priv->pdev = pdev;
274	priv->pdata = led_pdata;
275
276	return mlxreg_led_config(priv);
277}
278
279static int mlxreg_led_remove(struct platform_device *pdev)
280{
281	struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev);
282
283	mutex_destroy(&priv->access_lock);
284
285	return 0;
286}
287
288static struct platform_driver mlxreg_led_driver = {
289	.driver = {
290	    .name = "leds-mlxreg",
291	},
292	.probe = mlxreg_led_probe,
293	.remove = mlxreg_led_remove,
294};
295
296module_platform_driver(mlxreg_led_driver);
297
298MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
299MODULE_DESCRIPTION("Mellanox LED regmap driver");
300MODULE_LICENSE("Dual BSD/GPL");
301MODULE_ALIAS("platform:leds-mlxreg");