[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