Commit c0d2792b authored by Dave Airlie's avatar Dave Airlie

Merge tag 'atmel-hlcdc-drm-3.20' of https://github.com/bbrezillon/linux-at91 into drm-next

Add atmel HLCDC driver.

* tag 'atmel-hlcdc-drm-3.20' of https://github.com/bbrezillon/linux-at91:
  drm: add DT bindings documentation for atmel-hlcdc-dc driver
  drm: add Atmel HLCDC Display Controller support
  drm: panel: simple-panel: add bus format information for foxlink panel
  drm: panel: simple-panel: add support for bus_format retrieval
  drm: add bus_formats and num_bus_formats fields to drm_display_info
parents ca1130de 5b7e944e
Device-Tree bindings for Atmel's HLCDC (High LCD Controller) DRM driver
The Atmel HLCDC Display Controller is subdevice of the HLCDC MFD device.
See ../mfd/atmel-hlcdc.txt for more details.
Required properties:
- compatible: value should be "atmel,hlcdc-display-controller"
- pinctrl-names: the pin control state names. Should contain "default".
- pinctrl-0: should contain the default pinctrl states.
- #address-cells: should be set to 1.
- #size-cells: should be set to 0.
Required children nodes:
Children nodes are encoding available output ports and their connections
to external devices using the OF graph reprensentation (see ../graph.txt).
At least one port node is required.
Example:
hlcdc: hlcdc@f0030000 {
compatible = "atmel,sama5d3-hlcdc";
reg = <0xf0030000 0x2000>;
interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
clocks = <&lcdc_clk>, <&lcdck>, <&clk32k>;
clock-names = "periph_clk","sys_clk", "slow_clk";
status = "disabled";
hlcdc-display-controller {
compatible = "atmel,hlcdc-display-controller";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
#address-cells = <1>;
#size-cells = <0>;
port@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
hlcdc_panel_output: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_input>;
};
};
};
hlcdc_pwm: hlcdc-pwm {
compatible = "atmel,hlcdc-pwm";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcd_pwm>;
#pwm-cells = <3>;
};
};
......@@ -183,6 +183,8 @@ source "drivers/gpu/drm/cirrus/Kconfig"
source "drivers/gpu/drm/armada/Kconfig"
source "drivers/gpu/drm/atmel-hlcdc/Kconfig"
source "drivers/gpu/drm/rcar-du/Kconfig"
source "drivers/gpu/drm/shmobile/Kconfig"
......
......@@ -54,6 +54,7 @@ obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
obj-$(CONFIG_DRM_ARMADA) += armada/
obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
obj-$(CONFIG_DRM_OMAP) += omapdrm/
......
config DRM_ATMEL_HLCDC
tristate "DRM Support for ATMEL HLCDC Display Controller"
depends on DRM && OF && COMMON_CLK && MFD_ATMEL_HLCDC
select DRM_GEM_CMA_HELPER
select DRM_KMS_HELPER
select DRM_KMS_FB_HELPER
select DRM_KMS_CMA_HELPER
select DRM_PANEL
help
Choose this option if you have an ATMEL SoC with an HLCDC display
controller (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family).
atmel-hlcdc-dc-y := atmel_hlcdc_crtc.o \
atmel_hlcdc_dc.o \
atmel_hlcdc_layer.o \
atmel_hlcdc_output.o \
atmel_hlcdc_plane.o
obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc-dc.o
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2014 Traphandler
* Copyright (C) 2014 Free Electrons
* Copyright (C) 2014 Atmel
*
* Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com>
* Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DRM_ATMEL_HLCDC_H
#define DRM_ATMEL_HLCDC_H
#include <linux/clk.h>
#include <linux/irqdomain.h>
#include <linux/pwm.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_panel.h>
#include <drm/drmP.h>
#include "atmel_hlcdc_layer.h"
#define ATMEL_HLCDC_MAX_LAYERS 5
/**
* Atmel HLCDC Display Controller description structure.
*
* This structure describe the HLCDC IP capabilities and depends on the
* HLCDC IP version (or Atmel SoC family).
*
* @min_width: minimum width supported by the Display Controller
* @min_height: minimum height supported by the Display Controller
* @max_width: maximum width supported by the Display Controller
* @max_height: maximum height supported by the Display Controller
* @layers: a layer description table describing available layers
* @nlayers: layer description table size
*/
struct atmel_hlcdc_dc_desc {
int min_width;
int min_height;
int max_width;
int max_height;
const struct atmel_hlcdc_layer_desc *layers;
int nlayers;
};
/**
* Atmel HLCDC Plane properties.
*
* This structure stores plane property definitions.
*
* @alpha: alpha blending (or transparency) property
* @rotation: rotation property
*/
struct atmel_hlcdc_plane_properties {
struct drm_property *alpha;
struct drm_property *rotation;
};
/**
* Atmel HLCDC Plane.
*
* @base: base DRM plane structure
* @layer: HLCDC layer structure
* @properties: pointer to the property definitions structure
* @rotation: current rotation status
*/
struct atmel_hlcdc_plane {
struct drm_plane base;
struct atmel_hlcdc_layer layer;
struct atmel_hlcdc_plane_properties *properties;
unsigned int rotation;
};
static inline struct atmel_hlcdc_plane *
drm_plane_to_atmel_hlcdc_plane(struct drm_plane *p)
{
return container_of(p, struct atmel_hlcdc_plane, base);
}
static inline struct atmel_hlcdc_plane *
atmel_hlcdc_layer_to_plane(struct atmel_hlcdc_layer *l)
{
return container_of(l, struct atmel_hlcdc_plane, layer);
}
/**
* Atmel HLCDC Plane update request structure.
*
* @crtc_x: x position of the plane relative to the CRTC
* @crtc_y: y position of the plane relative to the CRTC
* @crtc_w: visible width of the plane
* @crtc_h: visible height of the plane
* @src_x: x buffer position
* @src_y: y buffer position
* @src_w: buffer width
* @src_h: buffer height
* @fb: framebuffer object object
* @bpp: bytes per pixel deduced from pixel_format
* @offsets: offsets to apply to the GEM buffers
* @xstride: value to add to the pixel pointer between each line
* @pstride: value to add to the pixel pointer between each pixel
* @nplanes: number of planes (deduced from pixel_format)
*/
struct atmel_hlcdc_plane_update_req {
int crtc_x;
int crtc_y;
unsigned int crtc_w;
unsigned int crtc_h;
uint32_t src_x;
uint32_t src_y;
uint32_t src_w;
uint32_t src_h;
struct drm_framebuffer *fb;
/* These fields are private and should not be touched */
int bpp[ATMEL_HLCDC_MAX_PLANES];
unsigned int offsets[ATMEL_HLCDC_MAX_PLANES];
int xstride[ATMEL_HLCDC_MAX_PLANES];
int pstride[ATMEL_HLCDC_MAX_PLANES];
int nplanes;
};
/**
* Atmel HLCDC Planes.
*
* This structure stores the instantiated HLCDC Planes and can be accessed by
* the HLCDC Display Controller or the HLCDC CRTC.
*
* @primary: primary plane
* @cursor: hardware cursor plane
* @overlays: overlay plane table
* @noverlays: number of overlay planes
*/
struct atmel_hlcdc_planes {
struct atmel_hlcdc_plane *primary;
struct atmel_hlcdc_plane *cursor;
struct atmel_hlcdc_plane **overlays;
int noverlays;
};
/**
* Atmel HLCDC Display Controller.
*
* @desc: HLCDC Display Controller description
* @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
* @fbdev: framebuffer device attached to the Display Controller
* @crtc: CRTC provided by the display controller
* @planes: instantiated planes
* @layers: active HLCDC layer
* @wq: display controller workqueue
*/
struct atmel_hlcdc_dc {
const struct atmel_hlcdc_dc_desc *desc;
struct atmel_hlcdc *hlcdc;
struct drm_fbdev_cma *fbdev;
struct drm_crtc *crtc;
struct atmel_hlcdc_planes *planes;
struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS];
struct workqueue_struct *wq;
};
extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats;
extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats;
int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc,
struct drm_display_mode *mode);
struct atmel_hlcdc_planes *
atmel_hlcdc_create_planes(struct drm_device *dev);
int atmel_hlcdc_plane_prepare_update_req(struct drm_plane *p,
struct atmel_hlcdc_plane_update_req *req,
const struct drm_display_mode *mode);
int atmel_hlcdc_plane_apply_update_req(struct drm_plane *p,
struct atmel_hlcdc_plane_update_req *req);
int atmel_hlcdc_plane_update_with_mode(struct drm_plane *p,
struct drm_crtc *crtc,
struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w,
unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h,
const struct drm_display_mode *mode);
void atmel_hlcdc_crtc_irq(struct drm_crtc *c);
void atmel_hlcdc_crtc_cancel_page_flip(struct drm_crtc *crtc,
struct drm_file *file);
int atmel_hlcdc_crtc_create(struct drm_device *dev);
int atmel_hlcdc_create_outputs(struct drm_device *dev);
#endif /* DRM_ATMEL_HLCDC_H */
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2014 Traphandler
* Copyright (C) 2014 Free Electrons
* Copyright (C) 2014 Atmel
*
* Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com>
* Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/of_graph.h>
#include <drm/drmP.h>
#include <drm/drm_panel.h>
#include "atmel_hlcdc_dc.h"
/**
* Atmel HLCDC RGB output mode
*/
enum atmel_hlcdc_connector_rgb_mode {
ATMEL_HLCDC_CONNECTOR_RGB444,
ATMEL_HLCDC_CONNECTOR_RGB565,
ATMEL_HLCDC_CONNECTOR_RGB666,
ATMEL_HLCDC_CONNECTOR_RGB888,
};
/**
* Atmel HLCDC RGB connector structure
*
* This structure stores RGB slave device information.
*
* @connector: DRM connector
* @encoder: DRM encoder
* @dc: pointer to the atmel_hlcdc_dc structure
* @dpms: current DPMS mode
*/
struct atmel_hlcdc_rgb_output {
struct drm_connector connector;
struct drm_encoder encoder;
struct atmel_hlcdc_dc *dc;
int dpms;
};
static inline struct atmel_hlcdc_rgb_output *
drm_connector_to_atmel_hlcdc_rgb_output(struct drm_connector *connector)
{
return container_of(connector, struct atmel_hlcdc_rgb_output,
connector);
}
static inline struct atmel_hlcdc_rgb_output *
drm_encoder_to_atmel_hlcdc_rgb_output(struct drm_encoder *encoder)
{
return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder);
}
/**
* Atmel HLCDC Panel device structure
*
* This structure is specialization of the slave device structure to
* interface with drm panels.
*
* @base: base slave device fields
* @panel: drm panel attached to this slave device
*/
struct atmel_hlcdc_panel {
struct atmel_hlcdc_rgb_output base;
struct drm_panel *panel;
};
static inline struct atmel_hlcdc_panel *
atmel_hlcdc_rgb_output_to_panel(struct atmel_hlcdc_rgb_output *output)
{
return container_of(output, struct atmel_hlcdc_panel, base);
}
static void atmel_hlcdc_panel_encoder_dpms(struct drm_encoder *encoder,
int mode)
{
struct atmel_hlcdc_rgb_output *rgb =
drm_encoder_to_atmel_hlcdc_rgb_output(encoder);
struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);
if (mode != DRM_MODE_DPMS_ON)
mode = DRM_MODE_DPMS_OFF;
if (mode == rgb->dpms)
return;
if (mode != DRM_MODE_DPMS_ON)
drm_panel_disable(panel->panel);
else
drm_panel_enable(panel->panel);
rgb->dpms = mode;
}
static bool
atmel_hlcdc_panel_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
return true;
}
static void atmel_hlcdc_panel_encoder_prepare(struct drm_encoder *encoder)
{
atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
}
static void atmel_hlcdc_panel_encoder_commit(struct drm_encoder *encoder)
{
atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
}
static void
atmel_hlcdc_rgb_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
struct atmel_hlcdc_rgb_output *rgb =
drm_encoder_to_atmel_hlcdc_rgb_output(encoder);
struct drm_display_info *info = &rgb->connector.display_info;
unsigned int cfg;
cfg = 0;
if (info->num_bus_formats) {
switch (info->bus_formats[0]) {
case MEDIA_BUS_FMT_RGB666_1X18:
cfg |= ATMEL_HLCDC_CONNECTOR_RGB666 << 8;
break;
case MEDIA_BUS_FMT_RGB888_1X24:
cfg |= ATMEL_HLCDC_CONNECTOR_RGB888 << 8;
break;
default:
break;
}
}
regmap_update_bits(rgb->dc->hlcdc->regmap, ATMEL_HLCDC_CFG(5),
ATMEL_HLCDC_MODE_MASK,
cfg);
}
static struct drm_encoder_helper_funcs atmel_hlcdc_panel_encoder_helper_funcs = {
.dpms = atmel_hlcdc_panel_encoder_dpms,
.mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup,
.prepare = atmel_hlcdc_panel_encoder_prepare,
.commit = atmel_hlcdc_panel_encoder_commit,
.mode_set = atmel_hlcdc_rgb_encoder_mode_set,
};
static void atmel_hlcdc_rgb_encoder_destroy(struct drm_encoder *encoder)
{
drm_encoder_cleanup(encoder);
memset(encoder, 0, sizeof(*encoder));
}
static const struct drm_encoder_funcs atmel_hlcdc_panel_encoder_funcs = {
.destroy = atmel_hlcdc_rgb_encoder_destroy,
};
static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector)
{
struct atmel_hlcdc_rgb_output *rgb =
drm_connector_to_atmel_hlcdc_rgb_output(connector);
struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);
return panel->panel->funcs->get_modes(panel->panel);
}
static int atmel_hlcdc_rgb_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct atmel_hlcdc_rgb_output *rgb =
drm_connector_to_atmel_hlcdc_rgb_output(connector);
return atmel_hlcdc_dc_mode_valid(rgb->dc, mode);
}
static struct drm_encoder *
atmel_hlcdc_rgb_best_encoder(struct drm_connector *connector)
{
struct atmel_hlcdc_rgb_output *rgb =
drm_connector_to_atmel_hlcdc_rgb_output(connector);
return &rgb->encoder;
}
static struct drm_connector_helper_funcs atmel_hlcdc_panel_connector_helper_funcs = {
.get_modes = atmel_hlcdc_panel_get_modes,
.mode_valid = atmel_hlcdc_rgb_mode_valid,
.best_encoder = atmel_hlcdc_rgb_best_encoder,
};
static enum drm_connector_status
atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static void
atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector)
{
struct atmel_hlcdc_rgb_output *rgb =
drm_connector_to_atmel_hlcdc_rgb_output(connector);
struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);
drm_panel_detach(panel->panel);
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs atmel_hlcdc_panel_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = atmel_hlcdc_panel_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = atmel_hlcdc_panel_connector_destroy,
};
static int atmel_hlcdc_create_panel_output(struct drm_device *dev,
struct of_endpoint *ep)
{
struct atmel_hlcdc_dc *dc = dev->dev_private;
struct device_node *np;
struct drm_panel *p = NULL;
struct atmel_hlcdc_panel *panel;
int ret;
np = of_graph_get_remote_port_parent(ep->local_node);
if (!np)
return -EINVAL;
p = of_drm_find_panel(np);
of_node_put(np);
if (!p)
return -EPROBE_DEFER;
panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL);
if (!panel)
return -EINVAL;
panel->base.dpms = DRM_MODE_DPMS_OFF;
panel->base.dc = dc;
drm_encoder_helper_add(&panel->base.encoder,
&atmel_hlcdc_panel_encoder_helper_funcs);
ret = drm_encoder_init(dev, &panel->base.encoder,
&atmel_hlcdc_panel_encoder_funcs,
DRM_MODE_ENCODER_LVDS);
if (ret)
return ret;
panel->base.connector.dpms = DRM_MODE_DPMS_OFF;
panel->base.connector.polled = DRM_CONNECTOR_POLL_CONNECT;
drm_connector_helper_add(&panel->base.connector,
&atmel_hlcdc_panel_connector_helper_funcs);
ret = drm_connector_init(dev, &panel->base.connector,
&atmel_hlcdc_panel_connector_funcs,
DRM_MODE_CONNECTOR_LVDS);
if (ret)
goto err_encoder_cleanup;
drm_mode_connector_attach_encoder(&panel->base.connector,
&panel->base.encoder);
panel->base.encoder.possible_crtcs = 0x1;
drm_panel_attach(p, &panel->base.connector);
panel->panel = p;
return 0;
err_encoder_cleanup:
drm_encoder_cleanup(&panel->base.encoder);
return ret;
}
int atmel_hlcdc_create_outputs(struct drm_device *dev)
{
struct device_node *port_np, *np;
struct of_endpoint ep;
int ret;
port_np = of_get_child_by_name(dev->dev->of_node, "port");
if (!port_np)
return -EINVAL;
np = of_get_child_by_name(port_np, "endpoint");
of_node_put(port_np);
if (!np)
return -EINVAL;
ret = of_graph_parse_endpoint(np, &ep);
of_node_put(port_np);
if (ret)
return ret;
/* We currently only support panel output */
return atmel_hlcdc_create_panel_output(dev, &ep);
}
This diff is collapsed.
......@@ -761,6 +761,40 @@ static void drm_mode_remove(struct drm_connector *connector,
drm_mode_destroy(connector->dev, mode);
}
/**
* drm_display_info_set_bus_formats - set the supported bus formats
* @info: display info to store bus formats in
* @fmts: array containing the supported bus formats
* @nfmts: the number of entries in the fmts array
*
* Store the supported bus formats in display info structure.
* See MEDIA_BUS_FMT_* definitions in include/uapi/linux/media-bus-format.h for
* a full list of available formats.
*/
int drm_display_info_set_bus_formats(struct drm_display_info *info,
const u32 *formats,
unsigned int num_formats)
{
u32 *fmts = NULL;
if (!formats && num_formats)
return -EINVAL;
if (formats && num_formats) {
fmts = kmemdup(formats, sizeof(*formats) * num_formats,
GFP_KERNEL);
if (!formats)
return -ENOMEM;
}
kfree(info->bus_formats);
info->bus_formats = fmts;
info->num_bus_formats = num_formats;
return 0;
}
EXPORT_SYMBOL(drm_display_info_set_bus_formats);
/**
* drm_connector_get_cmdline_mode - reads the user's cmdline mode
* @connector: connector to quwery
......@@ -923,6 +957,7 @@ void drm_connector_cleanup(struct drm_connector *connector)
ida_remove(&drm_connector_enum_list[connector->connector_type].ida,
connector->connector_type_id);
kfree(connector->display_info.bus_formats);