Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: Add AW20xx driver

This commit adds support for AWINIC AW20036/AW20054/AW20072 LED driver.
This driver supports following AW200XX features:
- Individual 64-level DIM currents

Signed-off-by: Martin Kurbanov <mmkurbanov@sberdevices.ru>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Link: https://lore.kernel.org/r/20230519130403.212479-3-mmkurbanov@sberdevices.ru
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Martin Kurbanov and committed by
Lee Jones
36a87f37 8f38f8fa

+613
+5
Documentation/ABI/testing/sysfs-class-led-driver-aw200xx
··· 1 + What: /sys/class/leds/<led>/dim 2 + Date: May 2023 3 + Description: 64-level DIM current. If you write a negative value or 4 + "auto", the dim will be calculated according to the 5 + brightness.
+13
drivers/leds/Kconfig
··· 94 94 95 95 Say Y to if your machine is a Dell Wyse 3020 thin client. 96 96 97 + config LEDS_AW200XX 98 + tristate "LED support for Awinic AW20036/AW20054/AW20072" 99 + depends on LEDS_CLASS 100 + depends on I2C 101 + help 102 + This option enables support for the AW20036/AW20054/AW20072 LED driver. 103 + It is a 3x12/6x9/6x12 matrix LED driver programmed via 104 + an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs, 105 + 3 pattern controllers for auto breathing or group dimming control. 106 + 107 + To compile this driver as a module, choose M here: the module 108 + will be called leds-aw200xx. 109 + 97 110 config LEDS_AW2013 98 111 tristate "LED support for Awinic AW2013" 99 112 depends on LEDS_CLASS && I2C && OF
+1
drivers/leds/Makefile
··· 14 14 obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o 15 15 obj-$(CONFIG_LEDS_APU) += leds-apu.o 16 16 obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o 17 + obj-$(CONFIG_LEDS_W200XX) += leds-aw200xx.o 17 18 obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o 18 19 obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o 19 20 obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
+594
drivers/leds/leds-aw200xx.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Awinic AW20036/AW20054/AW20072 LED driver 4 + * 5 + * Copyright (c) 2023, SberDevices. All Rights Reserved. 6 + * 7 + * Author: Martin Kurbanov <mmkurbanov@sberdevices.ru> 8 + */ 9 + 10 + #include <linux/bitfield.h> 11 + #include <linux/bits.h> 12 + #include <linux/container_of.h> 13 + #include <linux/i2c.h> 14 + #include <linux/leds.h> 15 + #include <linux/mod_devicetable.h> 16 + #include <linux/module.h> 17 + #include <linux/mutex.h> 18 + #include <linux/regmap.h> 19 + #include <linux/time.h> 20 + #include <linux/units.h> 21 + 22 + #define AW200XX_DIM_MAX (BIT(6) - 1) 23 + #define AW200XX_FADE_MAX (BIT(8) - 1) 24 + #define AW200XX_IMAX_DEFAULT_uA 60000 25 + #define AW200XX_IMAX_MAX_uA 160000 26 + #define AW200XX_IMAX_MIN_uA 3300 27 + 28 + /* Page 0 */ 29 + #define AW200XX_REG_PAGE0_BASE 0xc000 30 + 31 + /* Select page register */ 32 + #define AW200XX_REG_PAGE 0xF0 33 + #define AW200XX_PAGE_MASK (GENMASK(7, 6) | GENMASK(2, 0)) 34 + #define AW200XX_PAGE_SHIFT 0 35 + #define AW200XX_NUM_PAGES 6 36 + #define AW200XX_PAGE_SIZE 256 37 + #define AW200XX_REG(page, reg) \ 38 + (AW200XX_REG_PAGE0_BASE + (page) * AW200XX_PAGE_SIZE + (reg)) 39 + #define AW200XX_REG_MAX \ 40 + AW200XX_REG(AW200XX_NUM_PAGES - 1, AW200XX_PAGE_SIZE - 1) 41 + #define AW200XX_PAGE0 0 42 + #define AW200XX_PAGE1 1 43 + #define AW200XX_PAGE2 2 44 + #define AW200XX_PAGE3 3 45 + #define AW200XX_PAGE4 4 46 + #define AW200XX_PAGE5 5 47 + 48 + /* Chip ID register */ 49 + #define AW200XX_REG_IDR AW200XX_REG(AW200XX_PAGE0, 0x00) 50 + #define AW200XX_IDR_CHIPID 0x18 51 + 52 + /* Sleep mode register */ 53 + #define AW200XX_REG_SLPCR AW200XX_REG(AW200XX_PAGE0, 0x01) 54 + #define AW200XX_SLPCR_ACTIVE 0x00 55 + 56 + /* Reset register */ 57 + #define AW200XX_REG_RSTR AW200XX_REG(AW200XX_PAGE0, 0x02) 58 + #define AW200XX_RSTR_RESET 0x01 59 + 60 + /* Global current configuration register */ 61 + #define AW200XX_REG_GCCR AW200XX_REG(AW200XX_PAGE0, 0x03) 62 + #define AW200XX_GCCR_IMAX_MASK GENMASK(7, 4) 63 + #define AW200XX_GCCR_IMAX(x) ((x) << 4) 64 + #define AW200XX_GCCR_ALLON BIT(3) 65 + 66 + /* Fast clear display control register */ 67 + #define AW200XX_REG_FCD AW200XX_REG(AW200XX_PAGE0, 0x04) 68 + #define AW200XX_FCD_CLEAR 0x01 69 + 70 + /* Display size configuration */ 71 + #define AW200XX_REG_DSIZE AW200XX_REG(AW200XX_PAGE0, 0x80) 72 + #define AW200XX_DSIZE_COLUMNS_MAX 12 73 + 74 + #define AW200XX_LED2REG(x, columns) \ 75 + ((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns)))) 76 + 77 + /* 78 + * DIM current configuration register (page 4). 79 + * The even address for current DIM configuration. 80 + * The odd address for current FADE configuration 81 + */ 82 + #define AW200XX_REG_DIM(x, columns) \ 83 + AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2) 84 + #define AW200XX_REG_DIM2FADE(x) ((x) + 1) 85 + 86 + /* 87 + * Duty ratio of display scan (see p.15 of datasheet for formula): 88 + * duty = (592us / 600.5us) * (1 / (display_rows + 1)) 89 + * 90 + * Multiply to 1000 (MILLI) to improve the accuracy of calculations. 91 + */ 92 + #define AW200XX_DUTY_RATIO(rows) \ 93 + (((592UL * USEC_PER_SEC) / 600500UL) * (MILLI / (rows)) / MILLI) 94 + 95 + struct aw200xx_chipdef { 96 + u32 channels; 97 + u32 display_size_rows_max; 98 + u32 display_size_columns; 99 + }; 100 + 101 + struct aw200xx_led { 102 + struct led_classdev cdev; 103 + struct aw200xx *chip; 104 + int dim; 105 + u32 num; 106 + }; 107 + 108 + struct aw200xx { 109 + const struct aw200xx_chipdef *cdef; 110 + struct i2c_client *client; 111 + struct regmap *regmap; 112 + struct mutex mutex; 113 + u32 num_leds; 114 + u32 display_rows; 115 + struct aw200xx_led leds[]; 116 + }; 117 + 118 + static ssize_t dim_show(struct device *dev, struct device_attribute *devattr, 119 + char *buf) 120 + { 121 + struct led_classdev *cdev = dev_get_drvdata(dev); 122 + struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev); 123 + int dim = led->dim; 124 + 125 + if (dim < 0) 126 + return sysfs_emit(buf, "auto\n"); 127 + 128 + return sysfs_emit(buf, "%d\n", dim); 129 + } 130 + 131 + static ssize_t dim_store(struct device *dev, struct device_attribute *devattr, 132 + const char *buf, size_t count) 133 + { 134 + struct led_classdev *cdev = dev_get_drvdata(dev); 135 + struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev); 136 + struct aw200xx *chip = led->chip; 137 + u32 columns = chip->cdef->display_size_columns; 138 + int dim; 139 + ssize_t ret; 140 + 141 + if (sysfs_streq(buf, "auto")) { 142 + dim = -1; 143 + } else { 144 + ret = kstrtoint(buf, 0, &dim); 145 + if (ret) 146 + return ret; 147 + 148 + if (dim > AW200XX_DIM_MAX) 149 + return -EINVAL; 150 + } 151 + 152 + mutex_lock(&chip->mutex); 153 + 154 + if (dim >= 0) { 155 + ret = regmap_write(chip->regmap, 156 + AW200XX_REG_DIM(led->num, columns), dim); 157 + if (ret) 158 + goto out_unlock; 159 + } 160 + 161 + led->dim = dim; 162 + ret = count; 163 + 164 + out_unlock: 165 + mutex_unlock(&chip->mutex); 166 + return ret; 167 + } 168 + static DEVICE_ATTR_RW(dim); 169 + 170 + static struct attribute *dim_attrs[] = { 171 + &dev_attr_dim.attr, 172 + NULL 173 + }; 174 + ATTRIBUTE_GROUPS(dim); 175 + 176 + static int aw200xx_brightness_set(struct led_classdev *cdev, 177 + enum led_brightness brightness) 178 + { 179 + struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev); 180 + struct aw200xx *chip = led->chip; 181 + int dim; 182 + u32 reg; 183 + int ret; 184 + 185 + mutex_lock(&chip->mutex); 186 + 187 + reg = AW200XX_REG_DIM(led->num, chip->cdef->display_size_columns); 188 + 189 + dim = led->dim; 190 + if (dim < 0) 191 + dim = max_t(int, 192 + brightness / (AW200XX_FADE_MAX / AW200XX_DIM_MAX), 193 + 1); 194 + 195 + ret = regmap_write(chip->regmap, reg, dim); 196 + if (ret) 197 + goto out_unlock; 198 + 199 + ret = regmap_write(chip->regmap, 200 + AW200XX_REG_DIM2FADE(reg), brightness); 201 + 202 + out_unlock: 203 + mutex_unlock(&chip->mutex); 204 + 205 + return ret; 206 + } 207 + 208 + static u32 aw200xx_imax_from_global(const struct aw200xx *const chip, 209 + u32 global_imax_uA) 210 + { 211 + u64 led_imax_uA; 212 + 213 + /* 214 + * The output current of each LED (see p.14 of datasheet for formula): 215 + * Iled = Imax * (dim / 63) * ((fade + 1) / 256) * duty 216 + * 217 + * The value of duty is determined by the following formula: 218 + * duty = (592us / 600.5us) * (1 / (display_rows + 1)) 219 + * 220 + * Calculated for the maximum values of fade and dim. 221 + * We divide by 1000 because we earlier multiplied by 1000 to improve 222 + * accuracy when calculating the duty. 223 + */ 224 + led_imax_uA = global_imax_uA * AW200XX_DUTY_RATIO(chip->display_rows); 225 + do_div(led_imax_uA, MILLI); 226 + 227 + return led_imax_uA; 228 + } 229 + 230 + static u32 aw200xx_imax_to_global(const struct aw200xx *const chip, 231 + u32 led_imax_uA) 232 + { 233 + u32 duty = AW200XX_DUTY_RATIO(chip->display_rows); 234 + 235 + /* The output current of each LED (see p.14 of datasheet for formula) */ 236 + return (led_imax_uA * 1000U) / duty; 237 + } 238 + 239 + #define AW200XX_IMAX_MULTIPLIER1 10000 240 + #define AW200XX_IMAX_MULTIPLIER2 3333 241 + #define AW200XX_IMAX_BASE_VAL1 0 242 + #define AW200XX_IMAX_BASE_VAL2 8 243 + 244 + /* 245 + * The AW200XX has a 4-bit register (GCCR) to configure the global current, 246 + * which ranges from 3.3mA to 160mA. The following table indicates the values 247 + * of the global current, divided into two parts: 248 + * 249 + * +-----------+-----------------+-----------+-----------------+ 250 + * | reg value | global max (mA) | reg value | global max (mA) | 251 + * +-----------+-----------------+-----------+-----------------+ 252 + * | 0 | 10 | 8 | 3.3 | 253 + * | 1 | 20 | 9 | 6.7 | 254 + * | 2 | 30 | 10 | 10 | 255 + * | 3 | 40 | 11 | 13.3 | 256 + * | 4 | 60 | 12 | 20 | 257 + * | 5 | 80 | 13 | 26.7 | 258 + * | 6 | 120 | 14 | 40 | 259 + * | 7 | 160 | 15 | 53.3 | 260 + * +-----------+-----------------+-----------+-----------------+ 261 + * 262 + * The left part with a multiplier of 10, and the right part with a multiplier 263 + * of 3.3. 264 + * So we have two formulas to calculate the global current: 265 + * for the left part of the table: 266 + * imax = coefficient * 10 267 + * 268 + * for the right part of the table: 269 + * imax = coefficient * 3.3 270 + * 271 + * The coefficient table consists of the following values: 272 + * 1, 2, 3, 4, 6, 8, 12, 16. 273 + */ 274 + static int aw200xx_set_imax(const struct aw200xx *const chip, 275 + u32 led_imax_uA) 276 + { 277 + u32 g_imax_uA = aw200xx_imax_to_global(chip, led_imax_uA); 278 + u32 coeff_table[] = {1, 2, 3, 4, 6, 8, 12, 16}; 279 + u32 gccr_imax = UINT_MAX; 280 + u32 cur_imax = 0; 281 + int i; 282 + 283 + for (i = 0; i < ARRAY_SIZE(coeff_table); i++) { 284 + u32 imax; 285 + 286 + /* select closest ones */ 287 + imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER1; 288 + if (g_imax_uA >= imax && imax > cur_imax) { 289 + cur_imax = imax; 290 + gccr_imax = i + AW200XX_IMAX_BASE_VAL1; 291 + } 292 + 293 + imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER2; 294 + imax = DIV_ROUND_CLOSEST(imax, 100) * 100; 295 + if (g_imax_uA >= imax && imax > cur_imax) { 296 + cur_imax = imax; 297 + gccr_imax = i + AW200XX_IMAX_BASE_VAL2; 298 + } 299 + } 300 + 301 + if (gccr_imax == UINT_MAX) 302 + return -EINVAL; 303 + 304 + return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR, 305 + AW200XX_GCCR_IMAX_MASK, 306 + AW200XX_GCCR_IMAX(gccr_imax)); 307 + } 308 + 309 + static int aw200xx_chip_reset(const struct aw200xx *const chip) 310 + { 311 + int ret; 312 + 313 + ret = regmap_write(chip->regmap, AW200XX_REG_RSTR, AW200XX_RSTR_RESET); 314 + if (ret) 315 + return ret; 316 + 317 + regcache_mark_dirty(chip->regmap); 318 + return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR); 319 + } 320 + 321 + static int aw200xx_chip_init(const struct aw200xx *const chip) 322 + { 323 + int ret; 324 + 325 + ret = regmap_write(chip->regmap, AW200XX_REG_DSIZE, 326 + chip->display_rows - 1); 327 + if (ret) 328 + return ret; 329 + 330 + ret = regmap_write(chip->regmap, AW200XX_REG_SLPCR, 331 + AW200XX_SLPCR_ACTIVE); 332 + if (ret) 333 + return ret; 334 + 335 + return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR, 336 + AW200XX_GCCR_ALLON, AW200XX_GCCR_ALLON); 337 + } 338 + 339 + static int aw200xx_chip_check(const struct aw200xx *const chip) 340 + { 341 + struct device *dev = &chip->client->dev; 342 + u32 chipid; 343 + int ret; 344 + 345 + ret = regmap_read(chip->regmap, AW200XX_REG_IDR, &chipid); 346 + if (ret) 347 + return dev_err_probe(dev, ret, "Failed to read chip ID\n"); 348 + 349 + if (chipid != AW200XX_IDR_CHIPID) 350 + return dev_err_probe(dev, -ENODEV, 351 + "Chip reported wrong ID: %x\n", chipid); 352 + 353 + return 0; 354 + } 355 + 356 + static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) 357 + { 358 + struct fwnode_handle *child; 359 + u32 current_min, current_max, min_uA; 360 + int ret; 361 + int i; 362 + 363 + ret = device_property_read_u32(dev, "awinic,display-rows", 364 + &chip->display_rows); 365 + if (ret) 366 + return dev_err_probe(dev, ret, 367 + "Failed to read 'display-rows' property\n"); 368 + 369 + if (!chip->display_rows || 370 + chip->display_rows > chip->cdef->display_size_rows_max) { 371 + return dev_err_probe(dev, ret, 372 + "Invalid leds display size %u\n", 373 + chip->display_rows); 374 + } 375 + 376 + current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA); 377 + current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA); 378 + min_uA = UINT_MAX; 379 + i = 0; 380 + 381 + device_for_each_child_node(dev, child) { 382 + struct led_init_data init_data = {}; 383 + struct aw200xx_led *led; 384 + u32 source, imax; 385 + 386 + ret = fwnode_property_read_u32(child, "reg", &source); 387 + if (ret) { 388 + dev_err(dev, "Missing reg property\n"); 389 + chip->num_leds--; 390 + continue; 391 + } 392 + 393 + if (source >= chip->cdef->channels) { 394 + dev_err(dev, "LED reg %u out of range (max %u)\n", 395 + source, chip->cdef->channels); 396 + chip->num_leds--; 397 + continue; 398 + } 399 + 400 + ret = fwnode_property_read_u32(child, "led-max-microamp", 401 + &imax); 402 + if (ret) { 403 + dev_info(&chip->client->dev, 404 + "DT property led-max-microamp is missing\n"); 405 + } else if (imax < current_min || imax > current_max) { 406 + dev_err(dev, "Invalid value %u for led-max-microamp\n", 407 + imax); 408 + chip->num_leds--; 409 + continue; 410 + } else { 411 + min_uA = min(min_uA, imax); 412 + } 413 + 414 + led = &chip->leds[i]; 415 + led->dim = -1; 416 + led->num = source; 417 + led->chip = chip; 418 + led->cdev.brightness_set_blocking = aw200xx_brightness_set; 419 + led->cdev.groups = dim_groups; 420 + init_data.fwnode = child; 421 + 422 + ret = devm_led_classdev_register_ext(dev, &led->cdev, 423 + &init_data); 424 + if (ret) { 425 + fwnode_handle_put(child); 426 + break; 427 + } 428 + 429 + i++; 430 + } 431 + 432 + if (!chip->num_leds) 433 + return -EINVAL; 434 + 435 + if (min_uA == UINT_MAX) { 436 + min_uA = aw200xx_imax_from_global(chip, 437 + AW200XX_IMAX_DEFAULT_uA); 438 + } 439 + 440 + return aw200xx_set_imax(chip, min_uA); 441 + } 442 + 443 + static const struct regmap_range_cfg aw200xx_ranges[] = { 444 + { 445 + .name = "aw200xx", 446 + .range_min = 0, 447 + .range_max = AW200XX_REG_MAX, 448 + .selector_reg = AW200XX_REG_PAGE, 449 + .selector_mask = AW200XX_PAGE_MASK, 450 + .selector_shift = AW200XX_PAGE_SHIFT, 451 + .window_start = 0, 452 + .window_len = AW200XX_PAGE_SIZE, 453 + }, 454 + }; 455 + 456 + static const struct regmap_range aw200xx_writeonly_ranges[] = { 457 + regmap_reg_range(AW200XX_REG(AW200XX_PAGE1, 0x00), AW200XX_REG_MAX), 458 + }; 459 + 460 + static const struct regmap_access_table aw200xx_readable_table = { 461 + .no_ranges = aw200xx_writeonly_ranges, 462 + .n_no_ranges = ARRAY_SIZE(aw200xx_writeonly_ranges), 463 + }; 464 + 465 + static const struct regmap_range aw200xx_readonly_ranges[] = { 466 + regmap_reg_range(AW200XX_REG_IDR, AW200XX_REG_IDR), 467 + }; 468 + 469 + static const struct regmap_access_table aw200xx_writeable_table = { 470 + .no_ranges = aw200xx_readonly_ranges, 471 + .n_no_ranges = ARRAY_SIZE(aw200xx_readonly_ranges), 472 + }; 473 + 474 + static const struct regmap_config aw200xx_regmap_config = { 475 + .reg_bits = 8, 476 + .val_bits = 8, 477 + .max_register = AW200XX_REG_MAX, 478 + .ranges = aw200xx_ranges, 479 + .num_ranges = ARRAY_SIZE(aw200xx_ranges), 480 + .rd_table = &aw200xx_readable_table, 481 + .wr_table = &aw200xx_writeable_table, 482 + .cache_type = REGCACHE_RBTREE, 483 + }; 484 + 485 + static int aw200xx_probe(struct i2c_client *client) 486 + { 487 + const struct aw200xx_chipdef *cdef; 488 + struct aw200xx *chip; 489 + int count; 490 + int ret; 491 + 492 + cdef = device_get_match_data(&client->dev); 493 + if (!cdef) 494 + return -ENODEV; 495 + 496 + count = device_get_child_node_count(&client->dev); 497 + if (!count || count > cdef->channels) 498 + return dev_err_probe(&client->dev, -EINVAL, 499 + "Incorrect number of leds (%d)", count); 500 + 501 + chip = devm_kzalloc(&client->dev, struct_size(chip, leds, count), 502 + GFP_KERNEL); 503 + if (!chip) 504 + return -ENOMEM; 505 + 506 + chip->cdef = cdef; 507 + chip->num_leds = count; 508 + chip->client = client; 509 + i2c_set_clientdata(client, chip); 510 + 511 + chip->regmap = devm_regmap_init_i2c(client, &aw200xx_regmap_config); 512 + if (IS_ERR(chip->regmap)) 513 + return PTR_ERR(chip->regmap); 514 + 515 + ret = aw200xx_chip_check(chip); 516 + if (ret) 517 + return ret; 518 + 519 + mutex_init(&chip->mutex); 520 + 521 + /* Need a lock now since after call aw200xx_probe_fw, sysfs nodes created */ 522 + mutex_lock(&chip->mutex); 523 + 524 + ret = aw200xx_chip_reset(chip); 525 + if (ret) 526 + goto out_unlock; 527 + 528 + ret = aw200xx_probe_fw(&client->dev, chip); 529 + if (ret) 530 + goto out_unlock; 531 + 532 + ret = aw200xx_chip_init(chip); 533 + 534 + out_unlock: 535 + mutex_unlock(&chip->mutex); 536 + return ret; 537 + } 538 + 539 + static void aw200xx_remove(struct i2c_client *client) 540 + { 541 + struct aw200xx *chip = i2c_get_clientdata(client); 542 + 543 + aw200xx_chip_reset(chip); 544 + mutex_destroy(&chip->mutex); 545 + } 546 + 547 + static const struct aw200xx_chipdef aw20036_cdef = { 548 + .channels = 36, 549 + .display_size_rows_max = 3, 550 + .display_size_columns = 12, 551 + }; 552 + 553 + static const struct aw200xx_chipdef aw20054_cdef = { 554 + .channels = 54, 555 + .display_size_rows_max = 6, 556 + .display_size_columns = 9, 557 + }; 558 + 559 + static const struct aw200xx_chipdef aw20072_cdef = { 560 + .channels = 72, 561 + .display_size_rows_max = 6, 562 + .display_size_columns = 12, 563 + }; 564 + 565 + static const struct i2c_device_id aw200xx_id[] = { 566 + { "aw20036" }, 567 + { "aw20054" }, 568 + { "aw20072" }, 569 + {} 570 + }; 571 + MODULE_DEVICE_TABLE(i2c, aw200xx_id); 572 + 573 + static const struct of_device_id aw200xx_match_table[] = { 574 + { .compatible = "awinic,aw20036", .data = &aw20036_cdef, }, 575 + { .compatible = "awinic,aw20054", .data = &aw20054_cdef, }, 576 + { .compatible = "awinic,aw20072", .data = &aw20072_cdef, }, 577 + {} 578 + }; 579 + MODULE_DEVICE_TABLE(of, aw200xx_match_table); 580 + 581 + static struct i2c_driver aw200xx_driver = { 582 + .driver = { 583 + .name = "aw200xx", 584 + .of_match_table = aw200xx_match_table, 585 + }, 586 + .probe_new = aw200xx_probe, 587 + .remove = aw200xx_remove, 588 + .id_table = aw200xx_id, 589 + }; 590 + module_i2c_driver(aw200xx_driver); 591 + 592 + MODULE_AUTHOR("Martin Kurbanov <mmkurbanov@sberdevices.ru>"); 593 + MODULE_DESCRIPTION("AW200XX LED driver"); 594 + MODULE_LICENSE("GPL");