162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * LP5521/LP5523/LP55231/LP5562 Common Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2012 Texas Instruments 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Milo(Woogyom) Kim <milo.kim@ti.com> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Derived from leds-lp5521.c, leds-lp5523.c 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/clk.h> 1362306a36Sopenharmony_ci#include <linux/delay.h> 1462306a36Sopenharmony_ci#include <linux/firmware.h> 1562306a36Sopenharmony_ci#include <linux/i2c.h> 1662306a36Sopenharmony_ci#include <linux/leds.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/platform_data/leds-lp55xx.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 2162306a36Sopenharmony_ci#include <dt-bindings/leds/leds-lp55xx.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include "leds-lp55xx-common.h" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* External clock rate */ 2662306a36Sopenharmony_ci#define LP55XX_CLK_32K 32768 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic struct lp55xx_led *cdev_to_lp55xx_led(struct led_classdev *cdev) 2962306a36Sopenharmony_ci{ 3062306a36Sopenharmony_ci return container_of(cdev, struct lp55xx_led, cdev); 3162306a36Sopenharmony_ci} 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic struct lp55xx_led *dev_to_lp55xx_led(struct device *dev) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci return cdev_to_lp55xx_led(dev_get_drvdata(dev)); 3662306a36Sopenharmony_ci} 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic struct lp55xx_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci return container_of(mc_cdev, struct lp55xx_led, mc_cdev); 4162306a36Sopenharmony_ci} 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic void lp55xx_reset_device(struct lp55xx_chip *chip) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci struct lp55xx_device_config *cfg = chip->cfg; 4662306a36Sopenharmony_ci u8 addr = cfg->reset.addr; 4762306a36Sopenharmony_ci u8 val = cfg->reset.val; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* no error checking here because no ACK from the device after reset */ 5062306a36Sopenharmony_ci lp55xx_write(chip, addr, val); 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic int lp55xx_detect_device(struct lp55xx_chip *chip) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct lp55xx_device_config *cfg = chip->cfg; 5662306a36Sopenharmony_ci u8 addr = cfg->enable.addr; 5762306a36Sopenharmony_ci u8 val = cfg->enable.val; 5862306a36Sopenharmony_ci int ret; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci ret = lp55xx_write(chip, addr, val); 6162306a36Sopenharmony_ci if (ret) 6262306a36Sopenharmony_ci return ret; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci usleep_range(1000, 2000); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci ret = lp55xx_read(chip, addr, &val); 6762306a36Sopenharmony_ci if (ret) 6862306a36Sopenharmony_ci return ret; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (val != cfg->enable.val) 7162306a36Sopenharmony_ci return -ENODEV; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci return 0; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic int lp55xx_post_init_device(struct lp55xx_chip *chip) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct lp55xx_device_config *cfg = chip->cfg; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (!cfg->post_init_device) 8162306a36Sopenharmony_ci return 0; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci return cfg->post_init_device(chip); 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic ssize_t led_current_show(struct device *dev, 8762306a36Sopenharmony_ci struct device_attribute *attr, 8862306a36Sopenharmony_ci char *buf) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct lp55xx_led *led = dev_to_lp55xx_led(dev); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", led->led_current); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic ssize_t led_current_store(struct device *dev, 9662306a36Sopenharmony_ci struct device_attribute *attr, 9762306a36Sopenharmony_ci const char *buf, size_t len) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct lp55xx_led *led = dev_to_lp55xx_led(dev); 10062306a36Sopenharmony_ci struct lp55xx_chip *chip = led->chip; 10162306a36Sopenharmony_ci unsigned long curr; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if (kstrtoul(buf, 0, &curr)) 10462306a36Sopenharmony_ci return -EINVAL; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci if (curr > led->max_current) 10762306a36Sopenharmony_ci return -EINVAL; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if (!chip->cfg->set_led_current) 11062306a36Sopenharmony_ci return len; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci mutex_lock(&chip->lock); 11362306a36Sopenharmony_ci chip->cfg->set_led_current(led, (u8)curr); 11462306a36Sopenharmony_ci mutex_unlock(&chip->lock); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci return len; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic ssize_t max_current_show(struct device *dev, 12062306a36Sopenharmony_ci struct device_attribute *attr, 12162306a36Sopenharmony_ci char *buf) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci struct lp55xx_led *led = dev_to_lp55xx_led(dev); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", led->max_current); 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic DEVICE_ATTR_RW(led_current); 12962306a36Sopenharmony_cistatic DEVICE_ATTR_RO(max_current); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic struct attribute *lp55xx_led_attrs[] = { 13262306a36Sopenharmony_ci &dev_attr_led_current.attr, 13362306a36Sopenharmony_ci &dev_attr_max_current.attr, 13462306a36Sopenharmony_ci NULL, 13562306a36Sopenharmony_ci}; 13662306a36Sopenharmony_ciATTRIBUTE_GROUPS(lp55xx_led); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic int lp55xx_set_mc_brightness(struct led_classdev *cdev, 13962306a36Sopenharmony_ci enum led_brightness brightness) 14062306a36Sopenharmony_ci{ 14162306a36Sopenharmony_ci struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev); 14262306a36Sopenharmony_ci struct lp55xx_led *led = mcled_cdev_to_led(mc_dev); 14362306a36Sopenharmony_ci struct lp55xx_device_config *cfg = led->chip->cfg; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci led_mc_calc_color_components(&led->mc_cdev, brightness); 14662306a36Sopenharmony_ci return cfg->multicolor_brightness_fn(led); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic int lp55xx_set_brightness(struct led_classdev *cdev, 15162306a36Sopenharmony_ci enum led_brightness brightness) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci struct lp55xx_led *led = cdev_to_lp55xx_led(cdev); 15462306a36Sopenharmony_ci struct lp55xx_device_config *cfg = led->chip->cfg; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci led->brightness = (u8)brightness; 15762306a36Sopenharmony_ci return cfg->brightness_fn(led); 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic int lp55xx_init_led(struct lp55xx_led *led, 16162306a36Sopenharmony_ci struct lp55xx_chip *chip, int chan) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci struct lp55xx_platform_data *pdata = chip->pdata; 16462306a36Sopenharmony_ci struct lp55xx_device_config *cfg = chip->cfg; 16562306a36Sopenharmony_ci struct device *dev = &chip->cl->dev; 16662306a36Sopenharmony_ci int max_channel = cfg->max_channel; 16762306a36Sopenharmony_ci struct mc_subled *mc_led_info; 16862306a36Sopenharmony_ci struct led_classdev *led_cdev; 16962306a36Sopenharmony_ci char name[32]; 17062306a36Sopenharmony_ci int i; 17162306a36Sopenharmony_ci int ret; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (chan >= max_channel) { 17462306a36Sopenharmony_ci dev_err(dev, "invalid channel: %d / %d\n", chan, max_channel); 17562306a36Sopenharmony_ci return -EINVAL; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (pdata->led_config[chan].led_current == 0) 17962306a36Sopenharmony_ci return 0; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (pdata->led_config[chan].name) { 18262306a36Sopenharmony_ci led->cdev.name = pdata->led_config[chan].name; 18362306a36Sopenharmony_ci } else { 18462306a36Sopenharmony_ci snprintf(name, sizeof(name), "%s:channel%d", 18562306a36Sopenharmony_ci pdata->label ? : chip->cl->name, chan); 18662306a36Sopenharmony_ci led->cdev.name = name; 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci if (pdata->led_config[chan].num_colors > 1) { 19062306a36Sopenharmony_ci mc_led_info = devm_kcalloc(dev, 19162306a36Sopenharmony_ci pdata->led_config[chan].num_colors, 19262306a36Sopenharmony_ci sizeof(*mc_led_info), GFP_KERNEL); 19362306a36Sopenharmony_ci if (!mc_led_info) 19462306a36Sopenharmony_ci return -ENOMEM; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci led_cdev = &led->mc_cdev.led_cdev; 19762306a36Sopenharmony_ci led_cdev->name = led->cdev.name; 19862306a36Sopenharmony_ci led_cdev->brightness_set_blocking = lp55xx_set_mc_brightness; 19962306a36Sopenharmony_ci led->mc_cdev.num_colors = pdata->led_config[chan].num_colors; 20062306a36Sopenharmony_ci for (i = 0; i < led->mc_cdev.num_colors; i++) { 20162306a36Sopenharmony_ci mc_led_info[i].color_index = 20262306a36Sopenharmony_ci pdata->led_config[chan].color_id[i]; 20362306a36Sopenharmony_ci mc_led_info[i].channel = 20462306a36Sopenharmony_ci pdata->led_config[chan].output_num[i]; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci led->mc_cdev.subled_info = mc_led_info; 20862306a36Sopenharmony_ci } else { 20962306a36Sopenharmony_ci led->cdev.brightness_set_blocking = lp55xx_set_brightness; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci led->cdev.groups = lp55xx_led_groups; 21362306a36Sopenharmony_ci led->cdev.default_trigger = pdata->led_config[chan].default_trigger; 21462306a36Sopenharmony_ci led->led_current = pdata->led_config[chan].led_current; 21562306a36Sopenharmony_ci led->max_current = pdata->led_config[chan].max_current; 21662306a36Sopenharmony_ci led->chan_nr = pdata->led_config[chan].chan_nr; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci if (led->chan_nr >= max_channel) { 21962306a36Sopenharmony_ci dev_err(dev, "Use channel numbers between 0 and %d\n", 22062306a36Sopenharmony_ci max_channel - 1); 22162306a36Sopenharmony_ci return -EINVAL; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci if (pdata->led_config[chan].num_colors > 1) 22562306a36Sopenharmony_ci ret = devm_led_classdev_multicolor_register(dev, &led->mc_cdev); 22662306a36Sopenharmony_ci else 22762306a36Sopenharmony_ci ret = devm_led_classdev_register(dev, &led->cdev); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (ret) { 23062306a36Sopenharmony_ci dev_err(dev, "led register err: %d\n", ret); 23162306a36Sopenharmony_ci return ret; 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci return 0; 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic void lp55xx_firmware_loaded(const struct firmware *fw, void *context) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci struct lp55xx_chip *chip = context; 24062306a36Sopenharmony_ci struct device *dev = &chip->cl->dev; 24162306a36Sopenharmony_ci enum lp55xx_engine_index idx = chip->engine_idx; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if (!fw) { 24462306a36Sopenharmony_ci dev_err(dev, "firmware request failed\n"); 24562306a36Sopenharmony_ci return; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* handling firmware data is chip dependent */ 24962306a36Sopenharmony_ci mutex_lock(&chip->lock); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD; 25262306a36Sopenharmony_ci chip->fw = fw; 25362306a36Sopenharmony_ci if (chip->cfg->firmware_cb) 25462306a36Sopenharmony_ci chip->cfg->firmware_cb(chip); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci mutex_unlock(&chip->lock); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci /* firmware should be released for other channel use */ 25962306a36Sopenharmony_ci release_firmware(chip->fw); 26062306a36Sopenharmony_ci chip->fw = NULL; 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistatic int lp55xx_request_firmware(struct lp55xx_chip *chip) 26462306a36Sopenharmony_ci{ 26562306a36Sopenharmony_ci const char *name = chip->cl->name; 26662306a36Sopenharmony_ci struct device *dev = &chip->cl->dev; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci return request_firmware_nowait(THIS_MODULE, false, name, dev, 26962306a36Sopenharmony_ci GFP_KERNEL, chip, lp55xx_firmware_loaded); 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic ssize_t select_engine_show(struct device *dev, 27362306a36Sopenharmony_ci struct device_attribute *attr, 27462306a36Sopenharmony_ci char *buf) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); 27762306a36Sopenharmony_ci struct lp55xx_chip *chip = led->chip; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci return sprintf(buf, "%d\n", chip->engine_idx); 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic ssize_t select_engine_store(struct device *dev, 28362306a36Sopenharmony_ci struct device_attribute *attr, 28462306a36Sopenharmony_ci const char *buf, size_t len) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); 28762306a36Sopenharmony_ci struct lp55xx_chip *chip = led->chip; 28862306a36Sopenharmony_ci unsigned long val; 28962306a36Sopenharmony_ci int ret; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci if (kstrtoul(buf, 0, &val)) 29262306a36Sopenharmony_ci return -EINVAL; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci /* select the engine to be run */ 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci switch (val) { 29762306a36Sopenharmony_ci case LP55XX_ENGINE_1: 29862306a36Sopenharmony_ci case LP55XX_ENGINE_2: 29962306a36Sopenharmony_ci case LP55XX_ENGINE_3: 30062306a36Sopenharmony_ci mutex_lock(&chip->lock); 30162306a36Sopenharmony_ci chip->engine_idx = val; 30262306a36Sopenharmony_ci ret = lp55xx_request_firmware(chip); 30362306a36Sopenharmony_ci mutex_unlock(&chip->lock); 30462306a36Sopenharmony_ci break; 30562306a36Sopenharmony_ci default: 30662306a36Sopenharmony_ci dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n", val); 30762306a36Sopenharmony_ci return -EINVAL; 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci if (ret) { 31162306a36Sopenharmony_ci dev_err(dev, "request firmware err: %d\n", ret); 31262306a36Sopenharmony_ci return ret; 31362306a36Sopenharmony_ci } 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci return len; 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_cistatic inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start) 31962306a36Sopenharmony_ci{ 32062306a36Sopenharmony_ci if (chip->cfg->run_engine) 32162306a36Sopenharmony_ci chip->cfg->run_engine(chip, start); 32262306a36Sopenharmony_ci} 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_cistatic ssize_t run_engine_store(struct device *dev, 32562306a36Sopenharmony_ci struct device_attribute *attr, 32662306a36Sopenharmony_ci const char *buf, size_t len) 32762306a36Sopenharmony_ci{ 32862306a36Sopenharmony_ci struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); 32962306a36Sopenharmony_ci struct lp55xx_chip *chip = led->chip; 33062306a36Sopenharmony_ci unsigned long val; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci if (kstrtoul(buf, 0, &val)) 33362306a36Sopenharmony_ci return -EINVAL; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci /* run or stop the selected engine */ 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci if (val <= 0) { 33862306a36Sopenharmony_ci lp55xx_run_engine(chip, false); 33962306a36Sopenharmony_ci return len; 34062306a36Sopenharmony_ci } 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci mutex_lock(&chip->lock); 34362306a36Sopenharmony_ci lp55xx_run_engine(chip, true); 34462306a36Sopenharmony_ci mutex_unlock(&chip->lock); 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci return len; 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_cistatic DEVICE_ATTR_RW(select_engine); 35062306a36Sopenharmony_cistatic DEVICE_ATTR_WO(run_engine); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_cistatic struct attribute *lp55xx_engine_attributes[] = { 35362306a36Sopenharmony_ci &dev_attr_select_engine.attr, 35462306a36Sopenharmony_ci &dev_attr_run_engine.attr, 35562306a36Sopenharmony_ci NULL, 35662306a36Sopenharmony_ci}; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_cistatic const struct attribute_group lp55xx_engine_attr_group = { 35962306a36Sopenharmony_ci .attrs = lp55xx_engine_attributes, 36062306a36Sopenharmony_ci}; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ciint lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val) 36362306a36Sopenharmony_ci{ 36462306a36Sopenharmony_ci return i2c_smbus_write_byte_data(chip->cl, reg, val); 36562306a36Sopenharmony_ci} 36662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_write); 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ciint lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val) 36962306a36Sopenharmony_ci{ 37062306a36Sopenharmony_ci s32 ret; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci ret = i2c_smbus_read_byte_data(chip->cl, reg); 37362306a36Sopenharmony_ci if (ret < 0) 37462306a36Sopenharmony_ci return ret; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci *val = ret; 37762306a36Sopenharmony_ci return 0; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_read); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ciint lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, u8 mask, u8 val) 38262306a36Sopenharmony_ci{ 38362306a36Sopenharmony_ci int ret; 38462306a36Sopenharmony_ci u8 tmp; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci ret = lp55xx_read(chip, reg, &tmp); 38762306a36Sopenharmony_ci if (ret) 38862306a36Sopenharmony_ci return ret; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci tmp &= ~mask; 39162306a36Sopenharmony_ci tmp |= val & mask; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci return lp55xx_write(chip, reg, tmp); 39462306a36Sopenharmony_ci} 39562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_update_bits); 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_cibool lp55xx_is_extclk_used(struct lp55xx_chip *chip) 39862306a36Sopenharmony_ci{ 39962306a36Sopenharmony_ci struct clk *clk; 40062306a36Sopenharmony_ci int err; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci clk = devm_clk_get(&chip->cl->dev, "32k_clk"); 40362306a36Sopenharmony_ci if (IS_ERR(clk)) 40462306a36Sopenharmony_ci goto use_internal_clk; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci err = clk_prepare_enable(clk); 40762306a36Sopenharmony_ci if (err) 40862306a36Sopenharmony_ci goto use_internal_clk; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci if (clk_get_rate(clk) != LP55XX_CLK_32K) { 41162306a36Sopenharmony_ci clk_disable_unprepare(clk); 41262306a36Sopenharmony_ci goto use_internal_clk; 41362306a36Sopenharmony_ci } 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci dev_info(&chip->cl->dev, "%dHz external clock used\n", LP55XX_CLK_32K); 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci chip->clk = clk; 41862306a36Sopenharmony_ci return true; 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ciuse_internal_clk: 42162306a36Sopenharmony_ci dev_info(&chip->cl->dev, "internal clock used\n"); 42262306a36Sopenharmony_ci return false; 42362306a36Sopenharmony_ci} 42462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_is_extclk_used); 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ciint lp55xx_init_device(struct lp55xx_chip *chip) 42762306a36Sopenharmony_ci{ 42862306a36Sopenharmony_ci struct lp55xx_platform_data *pdata; 42962306a36Sopenharmony_ci struct lp55xx_device_config *cfg; 43062306a36Sopenharmony_ci struct device *dev = &chip->cl->dev; 43162306a36Sopenharmony_ci int ret = 0; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci WARN_ON(!chip); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci pdata = chip->pdata; 43662306a36Sopenharmony_ci cfg = chip->cfg; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci if (!pdata || !cfg) 43962306a36Sopenharmony_ci return -EINVAL; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci if (pdata->enable_gpiod) { 44262306a36Sopenharmony_ci gpiod_direction_output(pdata->enable_gpiod, 0); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci gpiod_set_consumer_name(pdata->enable_gpiod, "LP55xx enable"); 44562306a36Sopenharmony_ci gpiod_set_value(pdata->enable_gpiod, 0); 44662306a36Sopenharmony_ci usleep_range(1000, 2000); /* Keep enable down at least 1ms */ 44762306a36Sopenharmony_ci gpiod_set_value(pdata->enable_gpiod, 1); 44862306a36Sopenharmony_ci usleep_range(1000, 2000); /* 500us abs min. */ 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci lp55xx_reset_device(chip); 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci /* 45462306a36Sopenharmony_ci * Exact value is not available. 10 - 20ms 45562306a36Sopenharmony_ci * appears to be enough for reset. 45662306a36Sopenharmony_ci */ 45762306a36Sopenharmony_ci usleep_range(10000, 20000); 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci ret = lp55xx_detect_device(chip); 46062306a36Sopenharmony_ci if (ret) { 46162306a36Sopenharmony_ci dev_err(dev, "device detection err: %d\n", ret); 46262306a36Sopenharmony_ci goto err; 46362306a36Sopenharmony_ci } 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci /* chip specific initialization */ 46662306a36Sopenharmony_ci ret = lp55xx_post_init_device(chip); 46762306a36Sopenharmony_ci if (ret) { 46862306a36Sopenharmony_ci dev_err(dev, "post init device err: %d\n", ret); 46962306a36Sopenharmony_ci goto err_post_init; 47062306a36Sopenharmony_ci } 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci return 0; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_cierr_post_init: 47562306a36Sopenharmony_ci lp55xx_deinit_device(chip); 47662306a36Sopenharmony_cierr: 47762306a36Sopenharmony_ci return ret; 47862306a36Sopenharmony_ci} 47962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_init_device); 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_civoid lp55xx_deinit_device(struct lp55xx_chip *chip) 48262306a36Sopenharmony_ci{ 48362306a36Sopenharmony_ci struct lp55xx_platform_data *pdata = chip->pdata; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci if (chip->clk) 48662306a36Sopenharmony_ci clk_disable_unprepare(chip->clk); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci if (pdata->enable_gpiod) 48962306a36Sopenharmony_ci gpiod_set_value(pdata->enable_gpiod, 0); 49062306a36Sopenharmony_ci} 49162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_deinit_device); 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ciint lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci struct lp55xx_platform_data *pdata = chip->pdata; 49662306a36Sopenharmony_ci struct lp55xx_device_config *cfg = chip->cfg; 49762306a36Sopenharmony_ci int num_channels = pdata->num_channels; 49862306a36Sopenharmony_ci struct lp55xx_led *each; 49962306a36Sopenharmony_ci u8 led_current; 50062306a36Sopenharmony_ci int ret; 50162306a36Sopenharmony_ci int i; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci if (!cfg->brightness_fn) { 50462306a36Sopenharmony_ci dev_err(&chip->cl->dev, "empty brightness configuration\n"); 50562306a36Sopenharmony_ci return -EINVAL; 50662306a36Sopenharmony_ci } 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci for (i = 0; i < num_channels; i++) { 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci /* do not initialize channels that are not connected */ 51162306a36Sopenharmony_ci if (pdata->led_config[i].led_current == 0) 51262306a36Sopenharmony_ci continue; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci led_current = pdata->led_config[i].led_current; 51562306a36Sopenharmony_ci each = led + i; 51662306a36Sopenharmony_ci ret = lp55xx_init_led(each, chip, i); 51762306a36Sopenharmony_ci if (ret) 51862306a36Sopenharmony_ci goto err_init_led; 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci chip->num_leds++; 52162306a36Sopenharmony_ci each->chip = chip; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci /* setting led current at each channel */ 52462306a36Sopenharmony_ci if (cfg->set_led_current) 52562306a36Sopenharmony_ci cfg->set_led_current(each, led_current); 52662306a36Sopenharmony_ci } 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci return 0; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_cierr_init_led: 53162306a36Sopenharmony_ci return ret; 53262306a36Sopenharmony_ci} 53362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_register_leds); 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ciint lp55xx_register_sysfs(struct lp55xx_chip *chip) 53662306a36Sopenharmony_ci{ 53762306a36Sopenharmony_ci struct device *dev = &chip->cl->dev; 53862306a36Sopenharmony_ci struct lp55xx_device_config *cfg = chip->cfg; 53962306a36Sopenharmony_ci int ret; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci if (!cfg->run_engine || !cfg->firmware_cb) 54262306a36Sopenharmony_ci goto dev_specific_attrs; 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci ret = sysfs_create_group(&dev->kobj, &lp55xx_engine_attr_group); 54562306a36Sopenharmony_ci if (ret) 54662306a36Sopenharmony_ci return ret; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_cidev_specific_attrs: 54962306a36Sopenharmony_ci return cfg->dev_attr_group ? 55062306a36Sopenharmony_ci sysfs_create_group(&dev->kobj, cfg->dev_attr_group) : 0; 55162306a36Sopenharmony_ci} 55262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_register_sysfs); 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_civoid lp55xx_unregister_sysfs(struct lp55xx_chip *chip) 55562306a36Sopenharmony_ci{ 55662306a36Sopenharmony_ci struct device *dev = &chip->cl->dev; 55762306a36Sopenharmony_ci struct lp55xx_device_config *cfg = chip->cfg; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci if (cfg->dev_attr_group) 56062306a36Sopenharmony_ci sysfs_remove_group(&dev->kobj, cfg->dev_attr_group); 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci sysfs_remove_group(&dev->kobj, &lp55xx_engine_attr_group); 56362306a36Sopenharmony_ci} 56462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs); 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_cistatic int lp55xx_parse_common_child(struct device_node *np, 56762306a36Sopenharmony_ci struct lp55xx_led_config *cfg, 56862306a36Sopenharmony_ci int led_number, int *chan_nr) 56962306a36Sopenharmony_ci{ 57062306a36Sopenharmony_ci int ret; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci of_property_read_string(np, "chan-name", 57362306a36Sopenharmony_ci &cfg[led_number].name); 57462306a36Sopenharmony_ci of_property_read_u8(np, "led-cur", 57562306a36Sopenharmony_ci &cfg[led_number].led_current); 57662306a36Sopenharmony_ci of_property_read_u8(np, "max-cur", 57762306a36Sopenharmony_ci &cfg[led_number].max_current); 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci ret = of_property_read_u32(np, "reg", chan_nr); 58062306a36Sopenharmony_ci if (ret) 58162306a36Sopenharmony_ci return ret; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci if (*chan_nr < 0 || *chan_nr > cfg->max_channel) 58462306a36Sopenharmony_ci return -EINVAL; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci return 0; 58762306a36Sopenharmony_ci} 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_cistatic int lp55xx_parse_multi_led_child(struct device_node *child, 59062306a36Sopenharmony_ci struct lp55xx_led_config *cfg, 59162306a36Sopenharmony_ci int child_number, int color_number) 59262306a36Sopenharmony_ci{ 59362306a36Sopenharmony_ci int chan_nr, color_id, ret; 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci ret = lp55xx_parse_common_child(child, cfg, child_number, &chan_nr); 59662306a36Sopenharmony_ci if (ret) 59762306a36Sopenharmony_ci return ret; 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci ret = of_property_read_u32(child, "color", &color_id); 60062306a36Sopenharmony_ci if (ret) 60162306a36Sopenharmony_ci return ret; 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci cfg[child_number].color_id[color_number] = color_id; 60462306a36Sopenharmony_ci cfg[child_number].output_num[color_number] = chan_nr; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci return 0; 60762306a36Sopenharmony_ci} 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_cistatic int lp55xx_parse_multi_led(struct device_node *np, 61062306a36Sopenharmony_ci struct lp55xx_led_config *cfg, 61162306a36Sopenharmony_ci int child_number) 61262306a36Sopenharmony_ci{ 61362306a36Sopenharmony_ci struct device_node *child; 61462306a36Sopenharmony_ci int num_colors = 0, ret; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci for_each_available_child_of_node(np, child) { 61762306a36Sopenharmony_ci ret = lp55xx_parse_multi_led_child(child, cfg, child_number, 61862306a36Sopenharmony_ci num_colors); 61962306a36Sopenharmony_ci if (ret) { 62062306a36Sopenharmony_ci of_node_put(child); 62162306a36Sopenharmony_ci return ret; 62262306a36Sopenharmony_ci } 62362306a36Sopenharmony_ci num_colors++; 62462306a36Sopenharmony_ci } 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci cfg[child_number].num_colors = num_colors; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci return 0; 62962306a36Sopenharmony_ci} 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_cistatic int lp55xx_parse_logical_led(struct device_node *np, 63262306a36Sopenharmony_ci struct lp55xx_led_config *cfg, 63362306a36Sopenharmony_ci int child_number) 63462306a36Sopenharmony_ci{ 63562306a36Sopenharmony_ci int led_color, ret; 63662306a36Sopenharmony_ci int chan_nr = 0; 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci cfg[child_number].default_trigger = 63962306a36Sopenharmony_ci of_get_property(np, "linux,default-trigger", NULL); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci ret = of_property_read_u32(np, "color", &led_color); 64262306a36Sopenharmony_ci if (ret) 64362306a36Sopenharmony_ci return ret; 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci if (led_color == LED_COLOR_ID_RGB) 64662306a36Sopenharmony_ci return lp55xx_parse_multi_led(np, cfg, child_number); 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci ret = lp55xx_parse_common_child(np, cfg, child_number, &chan_nr); 64962306a36Sopenharmony_ci if (ret < 0) 65062306a36Sopenharmony_ci return ret; 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci cfg[child_number].chan_nr = chan_nr; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci return ret; 65562306a36Sopenharmony_ci} 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_cistruct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev, 65862306a36Sopenharmony_ci struct device_node *np, 65962306a36Sopenharmony_ci struct lp55xx_chip *chip) 66062306a36Sopenharmony_ci{ 66162306a36Sopenharmony_ci struct device_node *child; 66262306a36Sopenharmony_ci struct lp55xx_platform_data *pdata; 66362306a36Sopenharmony_ci struct lp55xx_led_config *cfg; 66462306a36Sopenharmony_ci int num_channels; 66562306a36Sopenharmony_ci int i = 0; 66662306a36Sopenharmony_ci int ret; 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); 66962306a36Sopenharmony_ci if (!pdata) 67062306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci num_channels = of_get_available_child_count(np); 67362306a36Sopenharmony_ci if (num_channels == 0) { 67462306a36Sopenharmony_ci dev_err(dev, "no LED channels\n"); 67562306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 67662306a36Sopenharmony_ci } 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci cfg = devm_kcalloc(dev, num_channels, sizeof(*cfg), GFP_KERNEL); 67962306a36Sopenharmony_ci if (!cfg) 68062306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci pdata->led_config = &cfg[0]; 68362306a36Sopenharmony_ci pdata->num_channels = num_channels; 68462306a36Sopenharmony_ci cfg->max_channel = chip->cfg->max_channel; 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci for_each_available_child_of_node(np, child) { 68762306a36Sopenharmony_ci ret = lp55xx_parse_logical_led(child, cfg, i); 68862306a36Sopenharmony_ci if (ret) { 68962306a36Sopenharmony_ci of_node_put(child); 69062306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 69162306a36Sopenharmony_ci } 69262306a36Sopenharmony_ci i++; 69362306a36Sopenharmony_ci } 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci if (of_property_read_u32(np, "ti,charge-pump-mode", &pdata->charge_pump_mode)) 69662306a36Sopenharmony_ci pdata->charge_pump_mode = LP55XX_CP_AUTO; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci if (pdata->charge_pump_mode > LP55XX_CP_AUTO) { 69962306a36Sopenharmony_ci dev_err(dev, "invalid charge pump mode %d\n", pdata->charge_pump_mode); 70062306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 70162306a36Sopenharmony_ci } 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci of_property_read_string(np, "label", &pdata->label); 70462306a36Sopenharmony_ci of_property_read_u8(np, "clock-mode", &pdata->clock_mode); 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci pdata->enable_gpiod = devm_gpiod_get_optional(dev, "enable", 70762306a36Sopenharmony_ci GPIOD_ASIS); 70862306a36Sopenharmony_ci if (IS_ERR(pdata->enable_gpiod)) 70962306a36Sopenharmony_ci return ERR_CAST(pdata->enable_gpiod); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci /* LP8501 specific */ 71262306a36Sopenharmony_ci of_property_read_u8(np, "pwr-sel", (u8 *)&pdata->pwr_sel); 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci return pdata; 71562306a36Sopenharmony_ci} 71662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata); 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ciMODULE_AUTHOR("Milo Kim <milo.kim@ti.com>"); 71962306a36Sopenharmony_ciMODULE_DESCRIPTION("LP55xx Common Driver"); 72062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 721