diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/video/of_display_timing.c | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/drivers/video/of_display_timing.c b/drivers/video/of_display_timing.c new file mode 100644 index 000000000..bebd371c6 --- /dev/null +++ b/drivers/video/of_display_timing.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * OF helpers for parsing display timings + * + * Copyright (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>, Pengutronix + * + * based on of_videomode.c by Sascha Hauer <s.hauer@pengutronix.de> + */ +#include <linux/export.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> + +/** + * parse_timing_property - parse timing_entry from device_node + * @np: device_node with the property + * @name: name of the property + * @result: will be set to the return value + * + * DESCRIPTION: + * Every display_timing can be specified with either just the typical value or + * a range consisting of min/typ/max. This function helps handling this + **/ +static int parse_timing_property(const struct device_node *np, const char *name, + struct timing_entry *result) +{ + struct property *prop; + int length, cells, ret; + + prop = of_find_property(np, name, &length); + if (!prop) { + pr_err("%pOF: could not find property %s\n", np, name); + return -EINVAL; + } + + cells = length / sizeof(u32); + if (cells == 1) { + ret = of_property_read_u32(np, name, &result->typ); + result->min = result->typ; + result->max = result->typ; + } else if (cells == 3) { + ret = of_property_read_u32_array(np, name, &result->min, cells); + } else { + pr_err("%pOF: illegal timing specification in %s\n", np, name); + return -EINVAL; + } + + return ret; +} + +/** + * of_parse_display_timing - parse display_timing entry from device_node + * @np: device_node with the properties + * @dt: display_timing that contains the result. I may be partially written in case of errors + **/ +static int of_parse_display_timing(const struct device_node *np, + struct display_timing *dt) +{ + u32 val = 0; + int ret = 0; + + memset(dt, 0, sizeof(*dt)); + + ret |= parse_timing_property(np, "hback-porch", &dt->hback_porch); + ret |= parse_timing_property(np, "hfront-porch", &dt->hfront_porch); + ret |= parse_timing_property(np, "hactive", &dt->hactive); + ret |= parse_timing_property(np, "hsync-len", &dt->hsync_len); + ret |= parse_timing_property(np, "vback-porch", &dt->vback_porch); + ret |= parse_timing_property(np, "vfront-porch", &dt->vfront_porch); + ret |= parse_timing_property(np, "vactive", &dt->vactive); + ret |= parse_timing_property(np, "vsync-len", &dt->vsync_len); + ret |= parse_timing_property(np, "clock-frequency", &dt->pixelclock); + + dt->flags = 0; + if (!of_property_read_u32(np, "vsync-active", &val)) + dt->flags |= val ? DISPLAY_FLAGS_VSYNC_HIGH : + DISPLAY_FLAGS_VSYNC_LOW; + if (!of_property_read_u32(np, "hsync-active", &val)) + dt->flags |= val ? DISPLAY_FLAGS_HSYNC_HIGH : + DISPLAY_FLAGS_HSYNC_LOW; + if (!of_property_read_u32(np, "de-active", &val)) + dt->flags |= val ? DISPLAY_FLAGS_DE_HIGH : + DISPLAY_FLAGS_DE_LOW; + if (!of_property_read_u32(np, "pixelclk-active", &val)) + dt->flags |= val ? DISPLAY_FLAGS_PIXDATA_POSEDGE : + DISPLAY_FLAGS_PIXDATA_NEGEDGE; + + if (!of_property_read_u32(np, "syncclk-active", &val)) + dt->flags |= val ? DISPLAY_FLAGS_SYNC_POSEDGE : + DISPLAY_FLAGS_SYNC_NEGEDGE; + else if (dt->flags & (DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_PIXDATA_NEGEDGE)) + dt->flags |= dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE ? + DISPLAY_FLAGS_SYNC_POSEDGE : + DISPLAY_FLAGS_SYNC_NEGEDGE; + + if (of_property_read_bool(np, "interlaced")) + dt->flags |= DISPLAY_FLAGS_INTERLACED; + if (of_property_read_bool(np, "doublescan")) + dt->flags |= DISPLAY_FLAGS_DOUBLESCAN; + if (of_property_read_bool(np, "doubleclk")) + dt->flags |= DISPLAY_FLAGS_DOUBLECLK; + + if (ret) { + pr_err("%pOF: error reading timing properties\n", np); + return -EINVAL; + } + + return 0; +} + +/** + * of_get_display_timing - parse a display_timing entry + * @np: device_node with the timing subnode + * @name: name of the timing node + * @dt: display_timing struct to fill + **/ +int of_get_display_timing(const struct device_node *np, const char *name, + struct display_timing *dt) +{ + struct device_node *timing_np; + int ret; + + if (!np) + return -EINVAL; + + timing_np = of_get_child_by_name(np, name); + if (!timing_np) + return -ENOENT; + + ret = of_parse_display_timing(timing_np, dt); + + of_node_put(timing_np); + + return ret; +} +EXPORT_SYMBOL_GPL(of_get_display_timing); + +/** + * of_get_display_timings - parse all display_timing entries from a device_node + * @np: device_node with the subnodes + **/ +struct display_timings *of_get_display_timings(const struct device_node *np) +{ + struct device_node *timings_np; + struct device_node *entry; + struct device_node *native_mode; + struct display_timings *disp; + + if (!np) + return NULL; + + timings_np = of_get_child_by_name(np, "display-timings"); + if (!timings_np) { + pr_err("%pOF: could not find display-timings node\n", np); + return NULL; + } + + disp = kzalloc(sizeof(*disp), GFP_KERNEL); + if (!disp) { + pr_err("%pOF: could not allocate struct disp'\n", np); + goto dispfail; + } + + entry = of_parse_phandle(timings_np, "native-mode", 0); + /* assume first child as native mode if none provided */ + if (!entry) + entry = of_get_next_child(timings_np, NULL); + /* if there is no child, it is useless to go on */ + if (!entry) { + pr_err("%pOF: no timing specifications given\n", np); + goto entryfail; + } + + pr_debug("%pOF: using %pOFn as default timing\n", np, entry); + + native_mode = entry; + + disp->num_timings = of_get_child_count(timings_np); + if (disp->num_timings == 0) { + /* should never happen, as entry was already found above */ + pr_err("%pOF: no timings specified\n", np); + goto entryfail; + } + + disp->timings = kcalloc(disp->num_timings, + sizeof(struct display_timing *), + GFP_KERNEL); + if (!disp->timings) { + pr_err("%pOF: could not allocate timings array\n", np); + goto entryfail; + } + + disp->num_timings = 0; + disp->native_mode = 0; + + for_each_child_of_node(timings_np, entry) { + struct display_timing *dt; + int r; + + dt = kmalloc(sizeof(*dt), GFP_KERNEL); + if (!dt) { + pr_err("%pOF: could not allocate display_timing struct\n", + np); + goto timingfail; + } + + r = of_parse_display_timing(entry, dt); + if (r) { + /* + * to not encourage wrong devicetrees, fail in case of + * an error + */ + pr_err("%pOF: error in timing %d\n", + np, disp->num_timings + 1); + kfree(dt); + goto timingfail; + } + + if (native_mode == entry) + disp->native_mode = disp->num_timings; + + disp->timings[disp->num_timings] = dt; + disp->num_timings++; + } + of_node_put(timings_np); + /* + * native_mode points to the device_node returned by of_parse_phandle + * therefore call of_node_put on it + */ + of_node_put(native_mode); + + pr_debug("%pOF: got %d timings. Using timing #%d as default\n", + np, disp->num_timings, + disp->native_mode + 1); + + return disp; + +timingfail: + of_node_put(native_mode); + display_timings_release(disp); + disp = NULL; +entryfail: + kfree(disp); +dispfail: + of_node_put(timings_np); + return NULL; +} +EXPORT_SYMBOL_GPL(of_get_display_timings); |