|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +#include <linux/regmap.h> |
| 3 | +#include <net/dsa.h> |
| 4 | + |
| 5 | +#include "qca8k.h" |
| 6 | +#include "qca8k_leds.h" |
| 7 | + |
| 8 | +static int |
| 9 | +qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info) |
| 10 | +{ |
| 11 | + switch (port_num) { |
| 12 | + case 0: |
| 13 | + reg_info->reg = QCA8K_LED_CTRL_REG(led_num); |
| 14 | + reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT; |
| 15 | + break; |
| 16 | + case 1: |
| 17 | + case 2: |
| 18 | + case 3: |
| 19 | + /* Port 123 are controlled on a different reg */ |
| 20 | + reg_info->reg = QCA8K_LED_CTRL3_REG; |
| 21 | + reg_info->shift = QCA8K_LED_PHY123_PATTERN_EN_SHIFT(port_num, led_num); |
| 22 | + break; |
| 23 | + case 4: |
| 24 | + reg_info->reg = QCA8K_LED_CTRL_REG(led_num); |
| 25 | + reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT; |
| 26 | + break; |
| 27 | + default: |
| 28 | + return -EINVAL; |
| 29 | + } |
| 30 | + |
| 31 | + return 0; |
| 32 | +} |
| 33 | + |
| 34 | +static int |
| 35 | +qca8k_led_brightness_set(struct qca8k_led *led, |
| 36 | + enum led_brightness brightness) |
| 37 | +{ |
| 38 | + struct qca8k_led_pattern_en reg_info; |
| 39 | + struct qca8k_priv *priv = led->priv; |
| 40 | + u32 mask, val; |
| 41 | + |
| 42 | + qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info); |
| 43 | + |
| 44 | + val = QCA8K_LED_ALWAYS_OFF; |
| 45 | + if (brightness) |
| 46 | + val = QCA8K_LED_ALWAYS_ON; |
| 47 | + |
| 48 | + /* HW regs to control brightness is special and port 1-2-3 |
| 49 | + * are placed in a different reg. |
| 50 | + * |
| 51 | + * To control port 0 brightness: |
| 52 | + * - the 2 bit (15, 14) of: |
| 53 | + * - QCA8K_LED_CTRL0_REG for led1 |
| 54 | + * - QCA8K_LED_CTRL1_REG for led2 |
| 55 | + * - QCA8K_LED_CTRL2_REG for led3 |
| 56 | + * |
| 57 | + * To control port 4: |
| 58 | + * - the 2 bit (31, 30) of: |
| 59 | + * - QCA8K_LED_CTRL0_REG for led1 |
| 60 | + * - QCA8K_LED_CTRL1_REG for led2 |
| 61 | + * - QCA8K_LED_CTRL2_REG for led3 |
| 62 | + * |
| 63 | + * To control port 1: |
| 64 | + * - the 2 bit at (9, 8) of QCA8K_LED_CTRL3_REG are used for led1 |
| 65 | + * - the 2 bit at (11, 10) of QCA8K_LED_CTRL3_REG are used for led2 |
| 66 | + * - the 2 bit at (13, 12) of QCA8K_LED_CTRL3_REG are used for led3 |
| 67 | + * |
| 68 | + * To control port 2: |
| 69 | + * - the 2 bit at (15, 14) of QCA8K_LED_CTRL3_REG are used for led1 |
| 70 | + * - the 2 bit at (17, 16) of QCA8K_LED_CTRL3_REG are used for led2 |
| 71 | + * - the 2 bit at (19, 18) of QCA8K_LED_CTRL3_REG are used for led3 |
| 72 | + * |
| 73 | + * To control port 3: |
| 74 | + * - the 2 bit at (21, 20) of QCA8K_LED_CTRL3_REG are used for led1 |
| 75 | + * - the 2 bit at (23, 22) of QCA8K_LED_CTRL3_REG are used for led2 |
| 76 | + * - the 2 bit at (25, 24) of QCA8K_LED_CTRL3_REG are used for led3 |
| 77 | + * |
| 78 | + * To abstract this and have less code, we use the port and led numm |
| 79 | + * to calculate the shift and the correct reg due to this problem of |
| 80 | + * not having a 1:1 map of LED with the regs. |
| 81 | + */ |
| 82 | + if (led->port_num == 0 || led->port_num == 4) { |
| 83 | + mask = QCA8K_LED_PATTERN_EN_MASK; |
| 84 | + val <<= QCA8K_LED_PATTERN_EN_SHIFT; |
| 85 | + } else { |
| 86 | + mask = QCA8K_LED_PHY123_PATTERN_EN_MASK; |
| 87 | + } |
| 88 | + |
| 89 | + return regmap_update_bits(priv->regmap, reg_info.reg, |
| 90 | + mask << reg_info.shift, |
| 91 | + val << reg_info.shift); |
| 92 | +} |
| 93 | + |
| 94 | +static int |
| 95 | +qca8k_cled_brightness_set_blocking(struct led_classdev *ldev, |
| 96 | + enum led_brightness brightness) |
| 97 | +{ |
| 98 | + struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev); |
| 99 | + |
| 100 | + return qca8k_led_brightness_set(led, brightness); |
| 101 | +} |
| 102 | + |
| 103 | +static enum led_brightness |
| 104 | +qca8k_led_brightness_get(struct qca8k_led *led) |
| 105 | +{ |
| 106 | + struct qca8k_led_pattern_en reg_info; |
| 107 | + struct qca8k_priv *priv = led->priv; |
| 108 | + u32 val; |
| 109 | + int ret; |
| 110 | + |
| 111 | + qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info); |
| 112 | + |
| 113 | + ret = regmap_read(priv->regmap, reg_info.reg, &val); |
| 114 | + if (ret) |
| 115 | + return 0; |
| 116 | + |
| 117 | + val >>= reg_info.shift; |
| 118 | + |
| 119 | + if (led->port_num == 0 || led->port_num == 4) { |
| 120 | + val &= QCA8K_LED_PATTERN_EN_MASK; |
| 121 | + val >>= QCA8K_LED_PATTERN_EN_SHIFT; |
| 122 | + } else { |
| 123 | + val &= QCA8K_LED_PHY123_PATTERN_EN_MASK; |
| 124 | + } |
| 125 | + |
| 126 | + /* Assume brightness ON only when the LED is set to always ON */ |
| 127 | + return val == QCA8K_LED_ALWAYS_ON; |
| 128 | +} |
| 129 | + |
| 130 | +static int |
| 131 | +qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num) |
| 132 | +{ |
| 133 | + struct fwnode_handle *led = NULL, *leds = NULL; |
| 134 | + struct led_init_data init_data = { }; |
| 135 | + struct dsa_switch *ds = priv->ds; |
| 136 | + enum led_default_state state; |
| 137 | + struct qca8k_led *port_led; |
| 138 | + int led_num, led_index; |
| 139 | + int ret; |
| 140 | + |
| 141 | + leds = fwnode_get_named_child_node(port, "leds"); |
| 142 | + if (!leds) { |
| 143 | + dev_dbg(priv->dev, "No Leds node specified in device tree for port %d!\n", |
| 144 | + port_num); |
| 145 | + return 0; |
| 146 | + } |
| 147 | + |
| 148 | + fwnode_for_each_child_node(leds, led) { |
| 149 | + /* Reg represent the led number of the port. |
| 150 | + * Each port can have at most 3 leds attached |
| 151 | + * Commonly: |
| 152 | + * 1. is gigabit led |
| 153 | + * 2. is mbit led |
| 154 | + * 3. additional status led |
| 155 | + */ |
| 156 | + if (fwnode_property_read_u32(led, "reg", &led_num)) |
| 157 | + continue; |
| 158 | + |
| 159 | + if (led_num >= QCA8K_LED_PORT_COUNT) { |
| 160 | + dev_warn(priv->dev, "Invalid LED reg %d defined for port %d", |
| 161 | + led_num, port_num); |
| 162 | + continue; |
| 163 | + } |
| 164 | + |
| 165 | + led_index = QCA8K_LED_PORT_INDEX(port_num, led_num); |
| 166 | + |
| 167 | + port_led = &priv->ports_led[led_index]; |
| 168 | + port_led->port_num = port_num; |
| 169 | + port_led->led_num = led_num; |
| 170 | + port_led->priv = priv; |
| 171 | + |
| 172 | + state = led_init_default_state_get(led); |
| 173 | + switch (state) { |
| 174 | + case LEDS_DEFSTATE_ON: |
| 175 | + port_led->cdev.brightness = 1; |
| 176 | + qca8k_led_brightness_set(port_led, 1); |
| 177 | + break; |
| 178 | + case LEDS_DEFSTATE_KEEP: |
| 179 | + port_led->cdev.brightness = |
| 180 | + qca8k_led_brightness_get(port_led); |
| 181 | + break; |
| 182 | + default: |
| 183 | + port_led->cdev.brightness = 0; |
| 184 | + qca8k_led_brightness_set(port_led, 0); |
| 185 | + } |
| 186 | + |
| 187 | + port_led->cdev.max_brightness = 1; |
| 188 | + port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking; |
| 189 | + init_data.default_label = ":port"; |
| 190 | + init_data.fwnode = led; |
| 191 | + init_data.devname_mandatory = true; |
| 192 | + init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d", ds->slave_mii_bus->id, |
| 193 | + port_num); |
| 194 | + if (!init_data.devicename) |
| 195 | + return -ENOMEM; |
| 196 | + |
| 197 | + ret = devm_led_classdev_register_ext(priv->dev, &port_led->cdev, &init_data); |
| 198 | + if (ret) |
| 199 | + dev_warn(priv->dev, "Failed to init LED %d for port %d", led_num, port_num); |
| 200 | + |
| 201 | + kfree(init_data.devicename); |
| 202 | + } |
| 203 | + |
| 204 | + return 0; |
| 205 | +} |
| 206 | + |
| 207 | +int |
| 208 | +qca8k_setup_led_ctrl(struct qca8k_priv *priv) |
| 209 | +{ |
| 210 | + struct fwnode_handle *ports, *port; |
| 211 | + int port_num; |
| 212 | + int ret; |
| 213 | + |
| 214 | + ports = device_get_named_child_node(priv->dev, "ports"); |
| 215 | + if (!ports) { |
| 216 | + dev_info(priv->dev, "No ports node specified in device tree!"); |
| 217 | + return 0; |
| 218 | + } |
| 219 | + |
| 220 | + fwnode_for_each_child_node(ports, port) { |
| 221 | + if (fwnode_property_read_u32(port, "reg", &port_num)) |
| 222 | + continue; |
| 223 | + |
| 224 | + /* Skip checking for CPU port 0 and CPU port 6 as not supported */ |
| 225 | + if (port_num == 0 || port_num == 6) |
| 226 | + continue; |
| 227 | + |
| 228 | + /* Each port can have at most 3 different leds attached. |
| 229 | + * Switch port starts from 0 to 6, but port 0 and 6 are CPU |
| 230 | + * port. The port index needs to be decreased by one to identify |
| 231 | + * the correct port for LED setup. |
| 232 | + */ |
| 233 | + ret = qca8k_parse_port_leds(priv, port, qca8k_port_to_phy(port_num)); |
| 234 | + if (ret) |
| 235 | + return ret; |
| 236 | + } |
| 237 | + |
| 238 | + return 0; |
| 239 | +} |
0 commit comments