[PATCH v3] drm/panel: add s6e3ha2 AMOLED panel driver
Thierry Reding
thierry.reding at gmail.com
Tue Feb 3 04:51:52 PST 2015
On Tue, Jan 27, 2015 at 10:48:32AM +0900, Hyungwon Hwang wrote:
> From: Donghwa Lee <dh09.lee at samsung.com>
>
> This patch adds MIPI-DSI based S6E3HA2 panel driver. This panel has
> 1440x2560 resolution in 5.7-inch physical panel.
>
> Signed-off-by: Donghwa Lee <dh09.lee at samsung.com>
> Signed-off-by: Hyungwon Hwang <human.hwang at samsung.com>
> Cc: Inki Dae <inki.dae at samsung.com>
> Cc: Sangbae Lee <sangbae90.lee at samsung.com>
> ---
> Changes for v2:
> - Fix errata in documentation and source code comments
> Changes for v3:
> - Remove the term LCD to clarify the sort of this panel
> - Rename lcd-en-gpios to panel-en-gpios to clarify the sort of this panel
> - Fix errata in documentation and source code comments
> .../devicetree/bindings/panel/samsung,s6e3ha2.txt | 49 ++
> drivers/gpu/drm/panel/Kconfig | 6 +
> drivers/gpu/drm/panel/Makefile | 1 +
> drivers/gpu/drm/panel/panel-s6e3ha2.c | 513 +++++++++++++++++++++
> 4 files changed, 569 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/panel/samsung,s6e3ha2.txt
> create mode 100644 drivers/gpu/drm/panel/panel-s6e3ha2.c
>
> diff --git a/Documentation/devicetree/bindings/panel/samsung,s6e3ha2.txt b/Documentation/devicetree/bindings/panel/samsung,s6e3ha2.txt
> new file mode 100644
> index 0000000..5210926
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/panel/samsung,s6e3ha2.txt
> @@ -0,0 +1,49 @@
> +Samsung S6E3HA2 5.7" 1440x2560 AMOLED panel
> +
> +Required properties:
> + - compatible: "samsung,s6e3ha2"
> + - reg: the virtual channel number of a DSI peripheral
> + - vdd3-supply: core voltage supply
> + - vci-supply: voltage supply for analog circuits
> + - reset-gpios: a GPIO spec for the reset pin
> + - panel-en-gpios: a GPIO spec for the panel enable pin
Why not "enable-gpios"? That would be much more in line with the
reset-gpios property.
> + - te-gpios: a GPIO spec for the tearing effect synchronization signal gpio pin
s/gpio/GPIO/
> +
> +Optional properties:
> + - display-timings: timings for the connected panel as described by [1]
> + - panel-width-mm: physical panel width [mm]
> + - panel-height-mm: physical panel height [mm]
If these are optional, what happens if they aren't there? The panel is
not likely to work in that case.
Also I think these should be hard-coded in the driver, because they are
implied by the "compatible" string.
> +The device node can contain one 'port' child node with one child
> +'endpoint' node, according to the bindings defined in [2]. This
> +node should describe panel's video bus.
The example doesn't show how to use that, why is it necessary?
> diff --git a/drivers/gpu/drm/panel/panel-s6e3ha2.c b/drivers/gpu/drm/panel/panel-s6e3ha2.c
[...]
> +struct s6e3ha2 {
> + struct device *dev;
> + struct drm_panel panel;
> +
> + struct regulator_bulk_data supplies[2];
> + struct gpio_desc *reset_gpio;
> + struct gpio_desc *panel_en_gpio;
> + struct videomode vm;
> + u32 width_mm;
> + u32 height_mm;
> +
> + /* This field is tested by functions directly accessing DSI bus before
> + * transfer, transfer is skipped if it is set. In case of transfer
> + * failure or unexpected response the field is set to error value.
> + * Such construct allows to eliminate many checks in higher level
> + * functions.
> + */
> + int error;
I hate myself for not NAK'ing the first patch that introduced this idiom
stronger. I think it's a really bad concept and you're not doing
yourself any favours by using it.
> +static void s6e3ha2_te_start_setting(struct s6e3ha2 *ctx)
> +{
> + s6e3ha2_dcs_write_seq_static(ctx, 0xb9, 0x10, 0x09, 0xff, 0x00, 0x09);
> +}
> +
> +
Gratuituous blank line.
> +static void s6e3ha2_panel_init(struct s6e3ha2 *ctx)
> +{
> + s6e3ha2_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
We have a standard function for this, please use it.
> + /* common setting */
> + s6e3ha2_dcs_write_seq_static(ctx, MIPI_DCS_SET_TEAR_ON);
Same here.
> + s6e3ha2_test_key_off_f0(ctx);
> + s6e3ha2_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
And here.
> +static int s6e3ha2_unprepare(struct drm_panel *panel)
> +{
> + struct s6e3ha2 *ctx = panel_to_s6e3ha2(panel);
> +
> + s6e3ha2_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
> + s6e3ha2_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
More standard functions.
> + msleep(40);
> +
> + s6e3ha2_clear_error(ctx);
> +
> + return s6e3ha2_power_off(ctx);
> +}
> +
> +static int s6e3ha2_power_on(struct s6e3ha2 *ctx)
> +{
> + int ret;
> +
> + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> + if (ret < 0)
> + return ret;
> +
> + msleep(25);
> +
> + gpiod_set_value(ctx->panel_en_gpio, 0);
> + usleep_range(5000, 6000);
> + gpiod_set_value(ctx->panel_en_gpio, 1);
> +
> + gpiod_set_value(ctx->reset_gpio, 1);
> + usleep_range(5000, 6000);
> + gpiod_set_value(ctx->reset_gpio, 0);
> + usleep_range(5000, 6000);
> + gpiod_set_value(ctx->reset_gpio, 1);
I would expect the value after power on to be 0 for reset. Perhaps you
need GPIO_ACTIVE_LOW in your device tree? Also why do you toggle thrice?
I would assume that putting the peripheral into reset and taking it out
again would be enough.
> +static int s6e3ha2_prepare(struct drm_panel *panel)
> +{
> + struct s6e3ha2 *ctx = panel_to_s6e3ha2(panel);
> + int ret;
> +
> + ret = s6e3ha2_power_on(ctx);
> + if (ret < 0)
> + return ret;
> +
> + s6e3ha2_panel_init(ctx);
> + if (ctx->error < 0)
This is one of the reasons why ctx->error is a bad idea. It's completely
unintuitive to check ctx->error here because nobody's actually setting
it in this context.
> + s6e3ha2_unprepare(panel);
This is somewhat asymmetric. I would expect the s6e3ha2_panel_init() to
undo what it did on failure, so that you would only have to call
s6e3ha2_power_off() here and undo what you did in *this* function.
> +static int s6e3ha2_enable(struct drm_panel *panel)
> +{
> + return 0;
> +}
This is really where you're supposed to turn on the backlight or
similar. Where does that happen?
> +static int s6e3ha2_get_modes(struct drm_panel *panel)
> +{
> + struct drm_connector *connector = panel->connector;
> + struct s6e3ha2 *ctx = panel_to_s6e3ha2(panel);
> + struct drm_display_mode *mode;
> +
> + mode = drm_mode_create(connector->dev);
> + if (!mode) {
> + DRM_ERROR("failed to create a new display mode\n");
> + return 0;
> + }
> +
> + drm_display_mode_from_videomode(&ctx->vm, mode);
> + mode->width_mm = ctx->width_mm;
> + mode->height_mm = ctx->height_mm;
> + connector->display_info.width_mm = mode->width_mm;
> + connector->display_info.height_mm = mode->height_mm;
> +
> + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> + drm_mode_probed_add(connector, mode);
> +
> + return 1;
> +}
Like I said before, the mode is implied by the compatible value, so no
need to parse it from device tree.
> +static int s6e3ha2_probe(struct mipi_dsi_device *dsi)
> +{
> + struct device *dev = &dsi->dev;
> + struct s6e3ha2 *ctx;
> + int ret;
> +
> + ctx = devm_kzalloc(dev, sizeof(struct s6e3ha2), GFP_KERNEL);
sizeof(*ctx) is much shorter.
> + ctx->reset_gpio = devm_gpiod_get(dev, "reset");
> + if (IS_ERR(ctx->reset_gpio)) {
> + dev_err(dev, "cannot get reset-gpios %ld\n",
> + PTR_ERR(ctx->reset_gpio));
> + return PTR_ERR(ctx->reset_gpio);
> + }
> + ret = gpiod_direction_output(ctx->reset_gpio, 1);
For consistency the above two lines should be separated by a blank line.
> + if (ret < 0) {
> + dev_err(dev, "cannot configure reset-gpios %d\n", ret);
> + return ret;
> + }
> +
> + ctx->panel_en_gpio = devm_gpiod_get(dev, "panel-en");
> + if (IS_ERR(ctx->panel_en_gpio)) {
> + dev_err(dev, "cannot get panel-en-gpios %ld\n",
> + PTR_ERR(ctx->panel_en_gpio));
> + return PTR_ERR(ctx->panel_en_gpio);
> + }
> + ret = gpiod_direction_output(ctx->panel_en_gpio, 1);
Same here.
> + if (ret < 0) {
> + dev_err(dev, "cannot configure panel-en-gpios %d\n", ret);
> + return ret;
> + }
You seem to be turning on the panel here. That's wrong because you're
supposed to wait for the display driver to tell you to turn it on via
->prepare() and ->enable().
> +
> + drm_panel_init(&ctx->panel);
> + ctx->panel.dev = dev;
> + ctx->panel.funcs = &s6e3ha2_drm_funcs;
I don't see a use for the _drm in here.
> +static struct of_device_id s6e3ha2_of_match[] = {
static const, please.
> + { .compatible = "samsung,s6e3ha2" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, s6e3ha2_of_match);
> +
> +static struct mipi_dsi_driver s6e3ha2_driver = {
> + .probe = s6e3ha2_probe,
> + .remove = s6e3ha2_remove,
> + .driver = {
> + .name = "panel_s6e3ha2",
Please use a - instead of an _ here, for consistency with other drivers.
> + .owner = THIS_MODULE,
No need for this anymore.
Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.freedesktop.org/archives/dri-devel/attachments/20150203/e5c8aa2f/attachment.sig>
More information about the dri-devel
mailing list