| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569 |
- /*
- * Copyright (c) 2006-2023, RT-Thread Development Team
- *
- * SPDX-License-Identifier: Apache-2.0
- *
- * Change Logs:
- * Date Author Notes
- * 2023-02-25 GuEe-GUI the first version
- */
- #include <rthw.h>
- #include <rtthread.h>
- #include <rtdevice.h>
- #define DBG_TAG "backlight.pwm"
- #define DBG_LVL DBG_INFO
- #include <rtdbg.h>
- struct pwm_backlight
- {
- struct rt_backlight_device parent;
- rt_uint32_t lth_brightness;
- rt_uint32_t *levels;
- rt_base_t enable_pin;
- rt_uint8_t active_val;
- rt_uint32_t scale;
- rt_uint32_t post_pwm_on_delay;
- rt_uint32_t pwm_off_delay;
- rt_uint32_t dft_brightness;
- rt_uint32_t max_brightness;
- rt_bool_t enabled;
- struct rt_device_pwm *pwm_dev;
- struct rt_pwm_configuration pwm_conf;
- struct rt_regulator *power_supply;
- };
- #define raw_to_pwm_backlight(raw) rt_container_of(raw, struct pwm_backlight, parent)
- static void pwm_backlight_power_on(struct pwm_backlight *pbl)
- {
- rt_err_t err;
- if (pbl->enabled)
- {
- return;
- }
- if (pbl->power_supply)
- {
- if ((err = rt_regulator_enable(pbl->power_supply)))
- {
- LOG_E("Enable power supply error = %s", rt_strerror(err));
- }
- }
- if (pbl->post_pwm_on_delay)
- {
- rt_thread_mdelay(pbl->post_pwm_on_delay);
- }
- if (pbl->enable_pin >= 0)
- {
- rt_pin_write(pbl->enable_pin, pbl->active_val);
- }
- pbl->enabled = RT_TRUE;
- }
- static void pwm_backlight_power_off(struct pwm_backlight *pbl)
- {
- if (!pbl->enabled)
- {
- return;
- }
- if (pbl->enable_pin >= 0)
- {
- rt_pin_write(pbl->enable_pin, !pbl->active_val);
- }
- if (pbl->pwm_off_delay)
- {
- rt_thread_mdelay(pbl->pwm_off_delay);
- }
- if (pbl->power_supply)
- {
- rt_regulator_disable(pbl->power_supply);
- }
- pbl->enabled = RT_FALSE;
- }
- static int compute_duty_cycle(struct pwm_backlight *pbl, rt_uint32_t brightness,
- rt_uint32_t period)
- {
- rt_uint64_t duty_cycle;
- rt_uint32_t lth = pbl->lth_brightness;
- if (pbl->levels)
- {
- duty_cycle = pbl->levels[brightness];
- }
- else
- {
- duty_cycle = brightness;
- }
- duty_cycle *= period - lth;
- rt_do_div(duty_cycle, pbl->scale);
- return duty_cycle + lth;
- }
- static rt_err_t pwm_backlight_update_status(struct rt_backlight_device *bl)
- {
- rt_uint32_t brightness, duty_cycle;
- struct rt_pwm_configuration pwm_conf = {};
- struct pwm_backlight *pbl = raw_to_pwm_backlight(bl);
- rt_pwm_get(pbl->pwm_dev, &pwm_conf);
- brightness = rt_backlight_power_brightness(bl);
- if (brightness > 0)
- {
- duty_cycle = compute_duty_cycle(pbl, brightness, pwm_conf.period);
- pwm_conf.pulse = duty_cycle;
- rt_pwm_set(pbl->pwm_dev, pwm_conf.channel, pwm_conf.period, pwm_conf.pulse);
- rt_pwm_enable(pbl->pwm_dev, pbl->pwm_conf.channel);
- pwm_backlight_power_on(pbl);
- }
- else
- {
- pwm_backlight_power_off(pbl);
- pwm_conf.pulse = 0;
- rt_pwm_set(pbl->pwm_dev, pwm_conf.channel, pwm_conf.period, pwm_conf.pulse);
- if (pbl->power_supply || pbl->enable_pin >= 0)
- {
- rt_pwm_disable(pbl->pwm_dev, pbl->pwm_conf.channel);
- }
- }
- return RT_EOK;
- }
- static struct rt_backlight_ops pwm_backlight_ops =
- {
- .update_status = pwm_backlight_update_status,
- };
- #define PWM_LUMINANCE_SHIFT 16
- #define PWM_LUMINANCE_SCALE (1 << PWM_LUMINANCE_SHIFT) /* luminance scale */
- rt_inline int period_fls(int period)
- {
- return period ? sizeof(period) * 8 - __rt_clz(period) : 0;
- }
- static rt_err_t pwm_backlight_brightness_default(struct pwm_backlight *pbl,
- rt_uint32_t period)
- {
- rt_uint32_t lightness;
- rt_uint64_t res, cie1931;
- pbl->max_brightness = rt_min((int)RT_DIV_ROUND_UP(period, period_fls(period)), 4096);
- pbl->levels = rt_calloc(pbl->max_brightness, sizeof(*pbl->levels));
- if (!pbl->levels)
- {
- return -RT_ENOMEM;
- }
- /* Fill the table using the cie1931 algorithm */
- for (int i = 0; i < pbl->max_brightness; ++i)
- {
- lightness = (i * PWM_LUMINANCE_SCALE) / pbl->max_brightness * 100;
- if (lightness <= (8 * PWM_LUMINANCE_SCALE))
- {
- cie1931 = RT_DIV_ROUND_CLOSEST(lightness * 10, 9033);
- }
- else
- {
- cie1931 = (lightness + (16 * PWM_LUMINANCE_SCALE)) / 116;
- cie1931 *= cie1931 * cie1931;
- cie1931 += 1ULL << (2 * PWM_LUMINANCE_SHIFT - 1);
- cie1931 >>= 2 * PWM_LUMINANCE_SHIFT;
- }
- res = cie1931 * period;
- res = RT_DIV_ROUND_CLOSEST_ULL(res, PWM_LUMINANCE_SCALE);
- if (res > RT_UINT32_MAX)
- {
- return -RT_EINVAL;
- }
- pbl->levels[i] = (rt_uint32_t)res;
- }
- pbl->dft_brightness = pbl->max_brightness / 2;
- pbl->max_brightness--;
- return 0;
- }
- static rt_err_t pwm_backlight_ofw_parse(struct pwm_backlight *pbl,
- struct rt_ofw_node *np)
- {
- rt_err_t err;
- rt_ssize_t length;
- rt_uint32_t *table, value;
- rt_uint32_t num_levels, num_steps = 0;
- struct rt_ofw_prop *prop;
- /*
- * These values are optional and set as 0 by default, the out values
- * are modified only if a valid u32 value can be decoded.
- */
- rt_ofw_prop_read_u32(np, "post-pwm-on-delay-ms", &pbl->post_pwm_on_delay);
- rt_ofw_prop_read_u32(np, "pwm-off-delay-ms", &pbl->pwm_off_delay);
- /*
- * Determine the number of brightness levels, if this property is not
- * set a default table of brightness levels will be used.
- */
- prop = rt_ofw_get_prop(np, "brightness-levels", &length);
- if (!prop)
- {
- return RT_EOK;
- }
- num_levels = length / sizeof(rt_uint32_t);
- if (!num_levels)
- {
- return RT_EOK;
- }
- pbl->levels = rt_calloc(num_levels, sizeof(*pbl->levels));
- if (!pbl->levels)
- {
- return -RT_ENOMEM;
- }
- if ((err = rt_ofw_prop_read_u32_array_index(np, "brightness-levels",
- 0, num_levels, pbl->levels)) < 0)
- {
- goto _fail;
- }
- if ((err = rt_ofw_prop_read_u32(np, "default-brightness-level", &value)))
- {
- goto _fail;
- }
- pbl->dft_brightness = value;
- /*
- * This property is optional, if is set enables linear
- * interpolation between each of the values of brightness levels
- * and creates a new pre-computed table.
- */
- rt_ofw_prop_read_u32(np, "num-interpolated-steps", &num_steps);
- /*
- * Make sure that there is at least two entries in the
- * brightness-levels table, otherwise we can't interpolate
- * between two points.
- */
- if (num_steps)
- {
- rt_int64_t dy;
- rt_uint32_t x1, x2, x, dx, y1, y2;
- rt_uint32_t num_input_levels = num_levels;
- if (num_input_levels < 2)
- {
- LOG_E("Can't interpolate");
- err = -RT_EINVAL;
- goto _fail;
- }
- num_levels = (num_input_levels - 1) * num_steps + 1;
- table = rt_calloc(num_levels, sizeof(*table));
- if (!table)
- {
- err = -RT_ENOMEM;
- goto _fail;
- }
- /*
- * Fill the interpolated table[x] = y
- * by draw lines between each (x1, y1) to (x2, y2).
- */
- dx = num_steps;
- for (int i = 0; i < num_input_levels - 1; ++i)
- {
- x1 = i * dx;
- x2 = x1 + dx;
- y1 = pbl->levels[i];
- y2 = pbl->levels[i + 1];
- dy = (rt_int64_t)y2 - y1;
- for (x = x1; x < x2; ++x)
- {
- table[x] = y1 + (rt_int64_t)(dy * (x - x1)) / (rt_int64_t)dx;
- }
- }
- /* Fill in the last point, since no line starts here. */
- table[x2] = y2;
- rt_free(pbl->levels);
- pbl->levels = table;
- }
- pbl->max_brightness = num_levels - 1;
- return RT_EOK;
- _fail:
- rt_free(pbl->levels);
- return err;
- }
- static enum rt_backlight_power pwm_backlight_initial_power_state(
- struct pwm_backlight *pbl, struct rt_device *dev)
- {
- rt_bool_t active = RT_TRUE;
- if (pbl->enable_pin >= 0 && rt_pin_read(pbl->enable_pin) != pbl->active_val)
- {
- active = RT_FALSE;
- }
- if (pbl->power_supply && !rt_regulator_is_enabled(pbl->power_supply))
- {
- active = RT_FALSE;
- }
- /* Synchronize the enable_gpio with the observed state of the hardware. */
- rt_pin_mode(pbl->enable_pin, PIN_MODE_OUTPUT);
- rt_pin_write(pbl->enable_pin, active ? pbl->active_val : !pbl->active_val);
- /* Not booted with device tree or no phandle link to the node */
- if (!dev->ofw_node || rt_dm_dev_prop_read_bool(dev, "phandle"))
- {
- return RT_BACKLIGHT_POWER_UNBLANK;
- }
- return active ? RT_BACKLIGHT_POWER_UNBLANK: RT_BACKLIGHT_POWER_POWERDOWN;
- }
- static rt_err_t pwm_backlight_probe(struct rt_platform_device *pdev)
- {
- rt_err_t err;
- enum rt_backlight_power power;
- struct rt_ofw_cell_args pwm_args;
- struct rt_device *dev = &pdev->parent;
- struct rt_ofw_node *np = dev->ofw_node, *pwm_np;
- struct pwm_backlight *pbl = rt_calloc(1, sizeof(*pbl));
- if (!pbl)
- {
- return -RT_ENOMEM;
- }
- if ((err = pwm_backlight_ofw_parse(pbl, dev->ofw_node)))
- {
- goto _fail;
- }
- pbl->enable_pin = rt_pin_get_named_pin(dev, "enable", 0, RT_NULL, &pbl->active_val);
- if (pbl->enable_pin < 0 && pbl->enable_pin != PIN_NONE)
- {
- err = pbl->enable_pin;
- goto _fail;
- }
- pbl->power_supply = rt_regulator_get(dev, "power");
- if (rt_is_err(pbl->power_supply))
- {
- err = rt_ptr_err(pbl->power_supply);
- goto _fail;
- }
- if (rt_ofw_parse_phandle_cells(np, "pwms", "#pwm-cells", 0, &pwm_args))
- {
- err = -RT_EINVAL;
- goto _fail;
- }
- pwm_np = pwm_args.data;
- if (!rt_ofw_data(pwm_np))
- {
- rt_platform_ofw_request(pwm_np);
- }
- pbl->pwm_dev = rt_ofw_data(pwm_np);
- rt_ofw_node_put(pwm_np);
- if (!pbl->pwm_dev)
- {
- err = -RT_EINVAL;
- goto _fail;
- }
- pbl->pwm_conf.channel = pwm_args.args[0];
- pbl->pwm_conf.period = pwm_args.args[1];
- rt_pwm_set_period(pbl->pwm_dev, pbl->pwm_conf.channel, pbl->pwm_conf.period);
- if (pbl->levels)
- {
- for (int i = 0; i <= pbl->max_brightness; ++i)
- {
- if (pbl->levels[i] > pbl->scale)
- {
- pbl->scale = pbl->levels[i];
- }
- }
- }
- else if (!pbl->max_brightness)
- {
- struct rt_pwm_configuration pwm_conf = {};
- rt_pwm_get(pbl->pwm_dev, &pwm_conf);
- /* Make levels */
- if ((err = pwm_backlight_brightness_default(pbl, pbl->pwm_conf.period)))
- {
- LOG_E("Setup default brightness table error = %s", rt_strerror(err));
- goto _fail;
- }
- for (int i = 0; i <= pbl->max_brightness; ++i)
- {
- if (pbl->levels[i] > pbl->scale)
- {
- pbl->scale = pbl->levels[i];
- }
- }
- }
- else
- {
- pbl->scale = pbl->max_brightness;
- }
- pbl->parent.props.max_brightness = pbl->max_brightness;
- pbl->parent.ops = &pwm_backlight_ops;
- if ((err = rt_backlight_register(&pbl->parent)))
- {
- goto _fail;
- }
- power = pwm_backlight_initial_power_state(pbl, dev);
- rt_backlight_set_power(&pbl->parent, power);
- if (pbl->dft_brightness > pbl->max_brightness)
- {
- LOG_W("Invalid default brightness level: %u, using %u",
- pbl->dft_brightness, pbl->max_brightness);
- pbl->dft_brightness = pbl->max_brightness;
- }
- rt_backlight_set_brightness(&pbl->parent, pbl->dft_brightness);
- return RT_EOK;
- _fail:
- if (!rt_is_err_or_null(pbl->power_supply))
- {
- rt_regulator_put(pbl->power_supply);
- }
- if (pbl->levels)
- {
- rt_free(pbl->levels);
- }
- rt_free(pbl);
- return err;
- }
- static rt_err_t pwm_backlight_remove(struct rt_platform_device *pdev)
- {
- struct rt_pwm_configuration pwm_conf = {};
- struct pwm_backlight *pbl = pdev->parent.user_data;
- rt_backlight_unregister(&pbl->parent);
- pwm_backlight_power_off(pbl);
- rt_regulator_put(pbl->power_supply);
- rt_pwm_get(pbl->pwm_dev, &pwm_conf);
- pwm_conf.pulse = 0;
- rt_pwm_set(pbl->pwm_dev, pwm_conf.channel, pwm_conf.period, pwm_conf.pulse);
- rt_pwm_disable(pbl->pwm_dev, pbl->pwm_conf.channel);
- if (pbl->levels)
- {
- rt_free(pbl->levels);
- }
- rt_free(pbl);
- return RT_EOK;
- }
- static rt_err_t pwm_backlight_shutdown(struct rt_platform_device *pdev)
- {
- struct rt_pwm_configuration pwm_conf = {};
- struct pwm_backlight *pbl = pdev->parent.user_data;
- pwm_backlight_power_off(pbl);
- rt_pwm_get(pbl->pwm_dev, &pwm_conf);
- pwm_conf.pulse = 0;
- rt_pwm_set(pbl->pwm_dev, pwm_conf.channel, pwm_conf.period, pwm_conf.pulse);
- rt_pwm_disable(pbl->pwm_dev, pbl->pwm_conf.channel);
- return RT_EOK;
- }
- static const struct rt_ofw_node_id pwm_backlight_ofw_ids[] =
- {
- { .compatible = "pwm-backlight" },
- { /* sentinel */ }
- };
- static struct rt_platform_driver pwm_backlight_driver =
- {
- .name = "pwm-backlight",
- .ids = pwm_backlight_ofw_ids,
- .probe = pwm_backlight_probe,
- .remove = pwm_backlight_remove,
- .shutdown = pwm_backlight_shutdown,
- };
- RT_PLATFORM_DRIVER_EXPORT(pwm_backlight_driver);
|