1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877
|
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Lightning Mountain SoC LED Serial Shift Output Controller driver
*
* Copyright (c) 2020 Intel Corporation.
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/sizes.h>
#include <linux/uaccess.h>
#define SSO_DEV_NAME "lgm-sso"
#define LED_BLINK_H8_0 0x0
#define LED_BLINK_H8_1 0x4
#define GET_FREQ_OFFSET(pin, src) (((pin) * 6) + ((src) * 2))
#define GET_SRC_OFFSET(pinc) (((pin) * 6) + 4)
#define DUTY_CYCLE(x) (0x8 + ((x) * 4))
#define SSO_CON0 0x2B0
#define SSO_CON0_RZFL BIT(26)
#define SSO_CON0_BLINK_R BIT(30)
#define SSO_CON0_SWU BIT(31)
#define SSO_CON1 0x2B4
#define SSO_CON1_FCDSC GENMASK(21, 20) /* Fixed Divider Shift Clock */
#define SSO_CON1_FPID GENMASK(24, 23)
#define SSO_CON1_GPTD GENMASK(26, 25)
#define SSO_CON1_US GENMASK(31, 30)
#define SSO_CPU 0x2B8
#define SSO_CON2 0x2C4
#define SSO_CON3 0x2C8
/* Driver MACRO */
#define MAX_PIN_NUM_PER_BANK SZ_32
#define MAX_GROUP_NUM SZ_4
#define PINS_PER_GROUP SZ_8
#define FPID_FREQ_RANK_MAX SZ_4
#define SSO_LED_MAX_NUM SZ_32
#define MAX_FREQ_RANK 10
#define DEF_GPTC_CLK_RATE 200000000
#define SSO_DEF_BRIGHTNESS LED_HALF
#define DATA_CLK_EDGE 0 /* 0-rising, 1-falling */
static const u32 freq_div_tbl[] = {4000, 2000, 1000, 800};
static const int freq_tbl[] = {2, 4, 8, 10, 50000, 100000, 200000, 250000};
static const int shift_clk_freq_tbl[] = {25000000, 12500000, 6250000, 3125000};
/*
* Update Source to update the SOUTs
* SW - Software has to update the SWU bit
* GPTC - General Purpose timer is used as clock source
* FPID - Divided FSC clock (FPID) is used as clock source
*/
enum {
US_SW = 0,
US_GPTC = 1,
US_FPID = 2
};
enum {
MAX_FPID_FREQ_RANK = 5, /* 1 to 4 */
MAX_GPTC_FREQ_RANK = 9, /* 5 to 8 */
MAX_GPTC_HS_FREQ_RANK = 10, /* 9 to 10 */
};
enum {
LED_GRP0_PIN_MAX = 24,
LED_GRP1_PIN_MAX = 29,
LED_GRP2_PIN_MAX = 32,
};
enum {
LED_GRP0_0_23,
LED_GRP1_24_28,
LED_GRP2_29_31,
LED_GROUP_MAX,
};
enum {
CLK_SRC_FPID = 0,
CLK_SRC_GPTC = 1,
CLK_SRC_GPTC_HS = 2,
};
struct sso_led_priv;
struct sso_led_desc {
const char *name;
const char *default_trigger;
unsigned int brightness;
unsigned int blink_rate;
unsigned int retain_state_suspended:1;
unsigned int retain_state_shutdown:1;
unsigned int panic_indicator:1;
unsigned int hw_blink:1;
unsigned int hw_trig:1;
unsigned int blinking:1;
int freq_idx;
u32 pin;
};
struct sso_led {
struct list_head list;
struct led_classdev cdev;
struct gpio_desc *gpiod;
struct sso_led_desc desc;
struct sso_led_priv *priv;
};
struct sso_gpio {
struct gpio_chip chip;
int shift_clk_freq;
int edge;
int freq;
u32 pins;
u32 alloc_bitmap;
};
struct sso_led_priv {
struct regmap *mmap;
struct device *dev;
struct platform_device *pdev;
struct clk_bulk_data clocks[2];
u32 fpid_clkrate;
u32 gptc_clkrate;
u32 freq[MAX_FREQ_RANK];
struct list_head led_list;
struct sso_gpio gpio;
};
static int sso_get_blink_rate_idx(struct sso_led_priv *priv, u32 rate)
{
int i;
for (i = 0; i < MAX_FREQ_RANK; i++) {
if (rate <= priv->freq[i])
return i;
}
return -1;
}
static unsigned int sso_led_pin_to_group(u32 pin)
{
if (pin < LED_GRP0_PIN_MAX)
return LED_GRP0_0_23;
else if (pin < LED_GRP1_PIN_MAX)
return LED_GRP1_24_28;
else
return LED_GRP2_29_31;
}
static u32 sso_led_get_freq_src(int freq_idx)
{
if (freq_idx < MAX_FPID_FREQ_RANK)
return CLK_SRC_FPID;
else if (freq_idx < MAX_GPTC_FREQ_RANK)
return CLK_SRC_GPTC;
else
return CLK_SRC_GPTC_HS;
}
static u32 sso_led_pin_blink_off(u32 pin, unsigned int group)
{
if (group == LED_GRP2_29_31)
return pin - LED_GRP1_PIN_MAX;
else if (group == LED_GRP1_24_28)
return pin - LED_GRP0_PIN_MAX;
else /* led 0 - 23 in led 32 location */
return SSO_LED_MAX_NUM - LED_GRP1_PIN_MAX;
}
static struct sso_led
*cdev_to_sso_led_data(struct led_classdev *led_cdev)
{
return container_of(led_cdev, struct sso_led, cdev);
}
static void sso_led_freq_set(struct sso_led_priv *priv, u32 pin, int freq_idx)
{
u32 reg, off, freq_src, val_freq;
u32 low, high, val;
unsigned int group;
if (!freq_idx)
return;
group = sso_led_pin_to_group(pin);
freq_src = sso_led_get_freq_src(freq_idx);
off = sso_led_pin_blink_off(pin, group);
if (group == LED_GRP0_0_23)
return;
else if (group == LED_GRP1_24_28)
reg = LED_BLINK_H8_0;
else
reg = LED_BLINK_H8_1;
if (freq_src == CLK_SRC_FPID)
val_freq = freq_idx - 1;
else if (freq_src == CLK_SRC_GPTC)
val_freq = freq_idx - MAX_FPID_FREQ_RANK;
/* set blink rate idx */
if (freq_src != CLK_SRC_GPTC_HS) {
low = GET_FREQ_OFFSET(off, freq_src);
high = low + 2;
val = val_freq << high;
regmap_update_bits(priv->mmap, reg, GENMASK(high, low), val);
}
/* select clock source */
low = GET_SRC_OFFSET(off);
high = low + 2;
val = freq_src << high;
regmap_update_bits(priv->mmap, reg, GENMASK(high, low), val);
}
static void sso_led_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct sso_led_priv *priv;
struct sso_led_desc *desc;
struct sso_led *led;
int val;
led = cdev_to_sso_led_data(led_cdev);
priv = led->priv;
desc = &led->desc;
desc->brightness = brightness;
regmap_write(priv->mmap, DUTY_CYCLE(desc->pin), brightness);
if (brightness == LED_OFF)
val = 0;
else
val = 1;
/* HW blink off */
if (desc->hw_blink && !val && desc->blinking) {
desc->blinking = 0;
regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin), 0);
} else if (desc->hw_blink && val && !desc->blinking) {
desc->blinking = 1;
regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin),
1 << desc->pin);
}
if (!desc->hw_trig)
gpiod_set_value(led->gpiod, val);
}
static enum led_brightness sso_led_brightness_get(struct led_classdev *led_cdev)
{
struct sso_led *led = cdev_to_sso_led_data(led_cdev);
return (enum led_brightness)led->desc.brightness;
}
static int
delay_to_freq_idx(struct sso_led *led, unsigned long *delay_on,
unsigned long *delay_off)
{
struct sso_led_priv *priv = led->priv;
unsigned long delay;
int freq_idx;
u32 freq;
if (!*delay_on && !*delay_off) {
*delay_on = *delay_off = (1000 / priv->freq[0]) / 2;
return 0;
}
delay = *delay_on + *delay_off;
freq = 1000 / delay;
freq_idx = sso_get_blink_rate_idx(priv, freq);
if (freq_idx == -1)
freq_idx = MAX_FREQ_RANK - 1;
delay = 1000 / priv->freq[freq_idx];
*delay_on = *delay_off = delay / 2;
if (!*delay_on)
*delay_on = *delay_off = 1;
return freq_idx;
}
static int
sso_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
unsigned long *delay_off)
{
struct sso_led_priv *priv;
struct sso_led *led;
int freq_idx;
led = cdev_to_sso_led_data(led_cdev);
priv = led->priv;
freq_idx = delay_to_freq_idx(led, delay_on, delay_off);
sso_led_freq_set(priv, led->desc.pin, freq_idx);
regmap_update_bits(priv->mmap, SSO_CON2, BIT(led->desc.pin),
1 << led->desc.pin);
led->desc.freq_idx = freq_idx;
led->desc.blink_rate = priv->freq[freq_idx];
led->desc.blinking = 1;
return 1;
}
static void sso_led_hw_cfg(struct sso_led_priv *priv, struct sso_led *led)
{
struct sso_led_desc *desc = &led->desc;
/* set freq */
if (desc->hw_blink) {
sso_led_freq_set(priv, desc->pin, desc->freq_idx);
regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin),
1 << desc->pin);
}
if (desc->hw_trig)
regmap_update_bits(priv->mmap, SSO_CON3, BIT(desc->pin),
1 << desc->pin);
/* set brightness */
regmap_write(priv->mmap, DUTY_CYCLE(desc->pin), desc->brightness);
/* enable output */
if (!desc->hw_trig && desc->brightness)
gpiod_set_value(led->gpiod, 1);
}
static int sso_create_led(struct sso_led_priv *priv, struct sso_led *led,
struct fwnode_handle *child)
{
struct sso_led_desc *desc = &led->desc;
struct led_init_data init_data;
int err;
init_data.fwnode = child;
init_data.devicename = SSO_DEV_NAME;
init_data.default_label = ":";
led->cdev.default_trigger = desc->default_trigger;
led->cdev.brightness_set = sso_led_brightness_set;
led->cdev.brightness_get = sso_led_brightness_get;
led->cdev.brightness = desc->brightness;
led->cdev.max_brightness = LED_FULL;
if (desc->retain_state_shutdown)
led->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;
if (desc->retain_state_suspended)
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
if (desc->panic_indicator)
led->cdev.flags |= LED_PANIC_INDICATOR;
if (desc->hw_blink)
led->cdev.blink_set = sso_led_blink_set;
sso_led_hw_cfg(priv, led);
err = devm_led_classdev_register_ext(priv->dev, &led->cdev, &init_data);
if (err)
return err;
list_add(&led->list, &priv->led_list);
return 0;
}
static void sso_init_freq(struct sso_led_priv *priv)
{
int i;
priv->freq[0] = 0;
for (i = 1; i < MAX_FREQ_RANK; i++) {
if (i < MAX_FPID_FREQ_RANK) {
priv->freq[i] = priv->fpid_clkrate / freq_div_tbl[i - 1];
} else if (i < MAX_GPTC_FREQ_RANK) {
priv->freq[i] = priv->gptc_clkrate /
freq_div_tbl[i - MAX_FPID_FREQ_RANK];
} else if (i < MAX_GPTC_HS_FREQ_RANK) {
priv->freq[i] = priv->gptc_clkrate;
}
}
}
static int sso_gpio_request(struct gpio_chip *chip, unsigned int offset)
{
struct sso_led_priv *priv = gpiochip_get_data(chip);
if (priv->gpio.alloc_bitmap & BIT(offset))
return -EINVAL;
priv->gpio.alloc_bitmap |= BIT(offset);
regmap_write(priv->mmap, DUTY_CYCLE(offset), 0xFF);
return 0;
}
static void sso_gpio_free(struct gpio_chip *chip, unsigned int offset)
{
struct sso_led_priv *priv = gpiochip_get_data(chip);
priv->gpio.alloc_bitmap &= ~BIT(offset);
regmap_write(priv->mmap, DUTY_CYCLE(offset), 0x0);
}
static int sso_gpio_get_dir(struct gpio_chip *chip, unsigned int offset)
{
return GPIO_LINE_DIRECTION_OUT;
}
static int
sso_gpio_dir_out(struct gpio_chip *chip, unsigned int offset, int value)
{
struct sso_led_priv *priv = gpiochip_get_data(chip);
bool bit = !!value;
regmap_update_bits(priv->mmap, SSO_CPU, BIT(offset), bit << offset);
if (!priv->gpio.freq)
regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU,
SSO_CON0_SWU);
return 0;
}
static int sso_gpio_get(struct gpio_chip *chip, unsigned int offset)
{
struct sso_led_priv *priv = gpiochip_get_data(chip);
u32 reg_val;
regmap_read(priv->mmap, SSO_CPU, ®_val);
return !!(reg_val & BIT(offset));
}
static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
{
struct sso_led_priv *priv = gpiochip_get_data(chip);
regmap_update_bits(priv->mmap, SSO_CPU, BIT(offset), value << offset);
if (!priv->gpio.freq)
regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU,
SSO_CON0_SWU);
}
static int sso_gpio_gc_init(struct device *dev, struct sso_led_priv *priv)
{
struct gpio_chip *gc = &priv->gpio.chip;
gc->request = sso_gpio_request;
gc->free = sso_gpio_free;
gc->get_direction = sso_gpio_get_dir;
gc->direction_output = sso_gpio_dir_out;
gc->get = sso_gpio_get;
gc->set = sso_gpio_set;
gc->label = "lgm-sso";
gc->base = -1;
/* To exclude pins from control, use "gpio-reserved-ranges" */
gc->ngpio = priv->gpio.pins;
gc->parent = dev;
gc->owner = THIS_MODULE;
return devm_gpiochip_add_data(dev, gc, priv);
}
static int sso_gpio_get_freq_idx(int freq)
{
int idx;
for (idx = 0; idx < ARRAY_SIZE(freq_tbl); idx++) {
if (freq <= freq_tbl[idx])
return idx;
}
return -1;
}
static void sso_register_shift_clk(struct sso_led_priv *priv)
{
int idx, size = ARRAY_SIZE(shift_clk_freq_tbl);
u32 val = 0;
for (idx = 0; idx < size; idx++) {
if (shift_clk_freq_tbl[idx] <= priv->gpio.shift_clk_freq) {
val = idx;
break;
}
}
if (idx == size)
dev_warn(priv->dev, "%s: Invalid freq %d\n",
__func__, priv->gpio.shift_clk_freq);
regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_FCDSC,
FIELD_PREP(SSO_CON1_FCDSC, val));
}
static int sso_gpio_freq_set(struct sso_led_priv *priv)
{
int freq_idx;
u32 val;
freq_idx = sso_gpio_get_freq_idx(priv->gpio.freq);
if (freq_idx == -1)
freq_idx = ARRAY_SIZE(freq_tbl) - 1;
val = freq_idx % FPID_FREQ_RANK_MAX;
if (!priv->gpio.freq) {
regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R, 0);
regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US,
FIELD_PREP(SSO_CON1_US, US_SW));
} else if (freq_idx < FPID_FREQ_RANK_MAX) {
regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R,
SSO_CON0_BLINK_R);
regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US,
FIELD_PREP(SSO_CON1_US, US_FPID));
regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_FPID,
FIELD_PREP(SSO_CON1_FPID, val));
} else {
regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R,
SSO_CON0_BLINK_R);
regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US,
FIELD_PREP(SSO_CON1_US, US_GPTC));
regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_GPTD,
FIELD_PREP(SSO_CON1_GPTD, val));
}
return 0;
}
static int sso_gpio_hw_init(struct sso_led_priv *priv)
{
u32 activate;
int i, err;
/* Clear all duty cycles */
for (i = 0; i < priv->gpio.pins; i++) {
err = regmap_write(priv->mmap, DUTY_CYCLE(i), 0);
if (err)
return err;
}
/* 4 groups for total 32 pins */
for (i = 1; i <= MAX_GROUP_NUM; i++) {
activate = !!(i * PINS_PER_GROUP <= priv->gpio.pins ||
priv->gpio.pins > (i - 1) * PINS_PER_GROUP);
err = regmap_update_bits(priv->mmap, SSO_CON1, BIT(i - 1),
activate << (i - 1));
if (err)
return err;
}
/* NO HW directly controlled pin by default */
err = regmap_write(priv->mmap, SSO_CON3, 0);
if (err)
return err;
/* NO BLINK for all pins */
err = regmap_write(priv->mmap, SSO_CON2, 0);
if (err)
return err;
/* OUTPUT 0 by default */
err = regmap_write(priv->mmap, SSO_CPU, 0);
if (err)
return err;
/* update edge */
err = regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_RZFL,
FIELD_PREP(SSO_CON0_RZFL, priv->gpio.edge));
if (err)
return err;
/* Set GPIO update rate */
sso_gpio_freq_set(priv);
/* Register shift clock */
sso_register_shift_clk(priv);
return 0;
}
static void sso_led_shutdown(struct sso_led *led)
{
struct sso_led_priv *priv = led->priv;
/* unregister led */
devm_led_classdev_unregister(priv->dev, &led->cdev);
/* clear HW control bit */
if (led->desc.hw_trig)
regmap_update_bits(priv->mmap, SSO_CON3, BIT(led->desc.pin), 0);
led->priv = NULL;
}
static int
__sso_led_dt_parse(struct sso_led_priv *priv, struct fwnode_handle *fw_ssoled)
{
struct fwnode_handle *fwnode_child;
struct device *dev = priv->dev;
struct sso_led_desc *desc;
struct sso_led *led;
const char *tmp;
u32 prop;
int ret;
fwnode_for_each_child_node(fw_ssoled, fwnode_child) {
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led) {
ret = -ENOMEM;
goto __dt_err;
}
INIT_LIST_HEAD(&led->list);
led->priv = priv;
desc = &led->desc;
led->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL,
fwnode_child,
GPIOD_ASIS, NULL);
if (IS_ERR(led->gpiod)) {
ret = dev_err_probe(dev, PTR_ERR(led->gpiod), "led: get gpio fail!\n");
goto __dt_err;
}
fwnode_property_read_string(fwnode_child,
"linux,default-trigger",
&desc->default_trigger);
if (fwnode_property_present(fwnode_child,
"retain-state-suspended"))
desc->retain_state_suspended = 1;
if (fwnode_property_present(fwnode_child,
"retain-state-shutdown"))
desc->retain_state_shutdown = 1;
if (fwnode_property_present(fwnode_child, "panic-indicator"))
desc->panic_indicator = 1;
ret = fwnode_property_read_u32(fwnode_child, "reg", &prop);
if (ret)
goto __dt_err;
if (prop >= SSO_LED_MAX_NUM) {
dev_err(dev, "invalid LED pin:%u\n", prop);
ret = -EINVAL;
goto __dt_err;
}
desc->pin = prop;
if (fwnode_property_present(fwnode_child, "intel,sso-hw-blink"))
desc->hw_blink = 1;
desc->hw_trig = fwnode_property_read_bool(fwnode_child,
"intel,sso-hw-trigger");
if (desc->hw_trig) {
desc->default_trigger = NULL;
desc->retain_state_shutdown = 0;
desc->retain_state_suspended = 0;
desc->panic_indicator = 0;
desc->hw_blink = 0;
}
if (fwnode_property_read_u32(fwnode_child,
"intel,sso-blink-rate-hz", &prop)) {
/* default first freq rate */
desc->freq_idx = 0;
desc->blink_rate = priv->freq[desc->freq_idx];
} else {
desc->freq_idx = sso_get_blink_rate_idx(priv, prop);
if (desc->freq_idx == -1)
desc->freq_idx = MAX_FREQ_RANK - 1;
desc->blink_rate = priv->freq[desc->freq_idx];
}
if (!fwnode_property_read_string(fwnode_child, "default-state", &tmp)) {
if (!strcmp(tmp, "on"))
desc->brightness = LED_FULL;
}
ret = sso_create_led(priv, led, fwnode_child);
if (ret)
goto __dt_err;
}
return 0;
__dt_err:
fwnode_handle_put(fwnode_child);
/* unregister leds */
list_for_each_entry(led, &priv->led_list, list)
sso_led_shutdown(led);
return ret;
}
static int sso_led_dt_parse(struct sso_led_priv *priv)
{
struct fwnode_handle *fwnode = dev_fwnode(priv->dev);
struct fwnode_handle *fw_ssoled;
struct device *dev = priv->dev;
int count;
int ret;
count = device_get_child_node_count(dev);
if (!count)
return 0;
fw_ssoled = fwnode_get_named_child_node(fwnode, "ssoled");
if (fw_ssoled) {
ret = __sso_led_dt_parse(priv, fw_ssoled);
fwnode_handle_put(fw_ssoled);
if (ret)
return ret;
}
return 0;
}
static int sso_probe_gpios(struct sso_led_priv *priv)
{
struct device *dev = priv->dev;
int ret;
if (device_property_read_u32(dev, "ngpios", &priv->gpio.pins))
priv->gpio.pins = MAX_PIN_NUM_PER_BANK;
if (priv->gpio.pins > MAX_PIN_NUM_PER_BANK)
return -EINVAL;
if (device_property_read_u32(dev, "intel,sso-update-rate-hz",
&priv->gpio.freq))
priv->gpio.freq = 0;
priv->gpio.edge = DATA_CLK_EDGE;
priv->gpio.shift_clk_freq = -1;
ret = sso_gpio_hw_init(priv);
if (ret)
return ret;
return sso_gpio_gc_init(dev, priv);
}
static void sso_clock_disable_unprepare(void *data)
{
struct sso_led_priv *priv = data;
clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clocks), priv->clocks);
}
static int intel_sso_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sso_led_priv *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->pdev = pdev;
priv->dev = dev;
/* gate clock */
priv->clocks[0].id = "sso";
/* fpid clock */
priv->clocks[1].id = "fpid";
ret = devm_clk_bulk_get(dev, ARRAY_SIZE(priv->clocks), priv->clocks);
if (ret) {
dev_err(dev, "Getting clocks failed!\n");
return ret;
}
ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clocks), priv->clocks);
if (ret) {
dev_err(dev, "Failed to prepare and enable clocks!\n");
return ret;
}
ret = devm_add_action_or_reset(dev, sso_clock_disable_unprepare, priv);
if (ret)
return ret;
priv->fpid_clkrate = clk_get_rate(priv->clocks[1].clk);
priv->mmap = syscon_node_to_regmap(dev->of_node);
priv->mmap = syscon_node_to_regmap(dev->of_node);
if (IS_ERR(priv->mmap)) {
dev_err(dev, "Failed to map iomem!\n");
return PTR_ERR(priv->mmap);
}
ret = sso_probe_gpios(priv);
if (ret) {
regmap_exit(priv->mmap);
return ret;
}
INIT_LIST_HEAD(&priv->led_list);
platform_set_drvdata(pdev, priv);
sso_init_freq(priv);
priv->gptc_clkrate = DEF_GPTC_CLK_RATE;
ret = sso_led_dt_parse(priv);
if (ret) {
regmap_exit(priv->mmap);
return ret;
}
dev_info(priv->dev, "sso LED init success!\n");
return 0;
}
static int intel_sso_led_remove(struct platform_device *pdev)
{
struct sso_led_priv *priv;
struct sso_led *led, *n;
priv = platform_get_drvdata(pdev);
list_for_each_entry_safe(led, n, &priv->led_list, list) {
list_del(&led->list);
sso_led_shutdown(led);
}
regmap_exit(priv->mmap);
return 0;
}
static const struct of_device_id of_sso_led_match[] = {
{ .compatible = "intel,lgm-ssoled" },
{}
};
MODULE_DEVICE_TABLE(of, of_sso_led_match);
static struct platform_driver intel_sso_led_driver = {
.probe = intel_sso_led_probe,
.remove = intel_sso_led_remove,
.driver = {
.name = "lgm-ssoled",
.of_match_table = of_sso_led_match,
},
};
module_platform_driver(intel_sso_led_driver);
MODULE_DESCRIPTION("Intel SSO LED/GPIO driver");
MODULE_LICENSE("GPL v2");
|