cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

leds-mlxreg.c (9013B)


      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");