Hi everybody,
Here's a new DRM driver for the Renesas SH Mobile display controller (a.k.a. LCDC). The hardware is pretty simple and consists of a single CRTC and four (non-scalable) planes that can be alpha-blended (first two planes only), overlayed or composed using ROP3.
The first two patches will go through the fbdev tree, but I've included them here in case someone woud like to compile the code. The third patch just defines two new formats and could already go in, unless we have a policy not to define formats until a driver uses them.
As this is my first attempt at writing a DRM driver I'm pretty sure I've made many mistakes. Review will as usual be very appreciated.
I've modified the modetest application from the libdrm repository to test this driver, I will submit patches for it right after cleaning them up.
Laurent Pinchart (4): fbdev: sh_mobile_meram: Add MERAM operations wrappers fbdev: sh_mobile_lcdc: Use MERAM operations wrappers drm: Add NV24 and NV42 pixel formats drm: Renesas SH Mobile DRM driver
drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/shmobile/Kconfig | 8 + drivers/gpu/drm/shmobile/Makefile | 8 + drivers/gpu/drm/shmobile/shmob_drm_backlight.c | 89 +++ drivers/gpu/drm/shmobile/shmob_drm_backlight.h | 23 + drivers/gpu/drm/shmobile/shmob_drm_crtc.c | 746 ++++++++++++++++++++++++ drivers/gpu/drm/shmobile/shmob_drm_crtc.h | 59 ++ drivers/gpu/drm/shmobile/shmob_drm_drv.c | 365 ++++++++++++ drivers/gpu/drm/shmobile/shmob_drm_drv.h | 52 ++ drivers/gpu/drm/shmobile/shmob_drm_gem.c | 195 ++++++ drivers/gpu/drm/shmobile/shmob_drm_gem.h | 46 ++ drivers/gpu/drm/shmobile/shmob_drm_kms.c | 211 +++++++ drivers/gpu/drm/shmobile/shmob_drm_kms.h | 44 ++ drivers/gpu/drm/shmobile/shmob_drm_plane.c | 240 ++++++++ drivers/gpu/drm/shmobile/shmob_drm_plane.h | 22 + drivers/gpu/drm/shmobile/shmob_drm_regs.h | 304 ++++++++++ drivers/video/sh_mobile_lcdcfb.c | 18 +- drivers/video/sh_mobile_meram.c | 24 +- include/drm/drm_fourcc.h | 2 + include/drm/shmob_drm.h | 99 ++++ include/video/sh_mobile_meram.h | 28 + 22 files changed, 2563 insertions(+), 23 deletions(-) create mode 100644 drivers/gpu/drm/shmobile/Kconfig create mode 100644 drivers/gpu/drm/shmobile/Makefile create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_backlight.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_backlight.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_crtc.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_crtc.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_drv.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_drv.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_gem.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_gem.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_kms.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_kms.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_plane.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_plane.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_regs.h create mode 100644 include/drm/shmob_drm.h
Remove direct dependency on the MERAM driver implementation by introducing inline wrappers for MERAM operations.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/sh_mobile_meram.c | 24 ++++++++++++------------ include/video/sh_mobile_meram.h | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 12 deletions(-)
diff --git a/drivers/video/sh_mobile_meram.c b/drivers/video/sh_mobile_meram.c index 82ba830..c51849f 100644 --- a/drivers/video/sh_mobile_meram.c +++ b/drivers/video/sh_mobile_meram.c @@ -444,11 +444,11 @@ static void meram_deinit(struct sh_mobile_meram_priv *priv, * Registration/unregistration */
-static void *sh_mobile_meram_register(struct sh_mobile_meram_info *pdata, - const struct sh_mobile_meram_cfg *cfg, - unsigned int xres, unsigned int yres, - unsigned int pixelformat, - unsigned int *pitch) +static void *__sh_mobile_meram_register(struct sh_mobile_meram_info *pdata, + const struct sh_mobile_meram_cfg *cfg, + unsigned int xres, unsigned int yres, + unsigned int pixelformat, + unsigned int *pitch) { struct sh_mobile_meram_fb_cache *cache; struct sh_mobile_meram_priv *priv = pdata->priv; @@ -495,7 +495,7 @@ err: }
static void -sh_mobile_meram_unregister(struct sh_mobile_meram_info *pdata, void *data) +__sh_mobile_meram_unregister(struct sh_mobile_meram_info *pdata, void *data) { struct sh_mobile_meram_fb_cache *cache = data; struct sh_mobile_meram_priv *priv = pdata->priv; @@ -513,9 +513,9 @@ sh_mobile_meram_unregister(struct sh_mobile_meram_info *pdata, void *data) }
static void -sh_mobile_meram_update(struct sh_mobile_meram_info *pdata, void *data, - unsigned long base_addr_y, unsigned long base_addr_c, - unsigned long *icb_addr_y, unsigned long *icb_addr_c) +__sh_mobile_meram_update(struct sh_mobile_meram_info *pdata, void *data, + unsigned long base_addr_y, unsigned long base_addr_c, + unsigned long *icb_addr_y, unsigned long *icb_addr_c) { struct sh_mobile_meram_fb_cache *cache = data; struct sh_mobile_meram_priv *priv = pdata->priv; @@ -530,9 +530,9 @@ sh_mobile_meram_update(struct sh_mobile_meram_info *pdata, void *data,
static struct sh_mobile_meram_ops sh_mobile_meram_ops = { .module = THIS_MODULE, - .meram_register = sh_mobile_meram_register, - .meram_unregister = sh_mobile_meram_unregister, - .meram_update = sh_mobile_meram_update, + .meram_register = __sh_mobile_meram_register, + .meram_unregister = __sh_mobile_meram_unregister, + .meram_update = __sh_mobile_meram_update, };
/* ----------------------------------------------------------------------------- diff --git a/include/video/sh_mobile_meram.h b/include/video/sh_mobile_meram.h index 29b2fd3..b03f823 100644 --- a/include/video/sh_mobile_meram.h +++ b/include/video/sh_mobile_meram.h @@ -60,4 +60,32 @@ struct sh_mobile_meram_ops { unsigned long *icb_addr_c); };
+static inline void * +sh_mobile_meram_register(struct sh_mobile_meram_info *mdev, + const struct sh_mobile_meram_cfg *cfg, + unsigned int xres, unsigned int yres, + unsigned int pixelformat, unsigned int *pitch) +{ + if (mdev == NULL || mdev->ops == NULL || cfg == NULL) + return NULL; + + return mdev->ops->meram_register(mdev, cfg, xres, yres, pixelformat, + pitch); +} + +static inline void +sh_mobile_meram_unregister(struct sh_mobile_meram_info *mdev, void *data) +{ + mdev->ops->meram_unregister(mdev, data); +} + +static inline void +sh_mobile_meram_update(struct sh_mobile_meram_info *mdev, void *data, + unsigned long base_addr_y, unsigned long base_addr_c, + unsigned long *icb_addr_y, unsigned long *icb_addr_c) +{ + mdev->ops->meram_update(mdev, data, base_addr_y, base_addr_c, + icb_addr_y, icb_addr_c); +} + #endif /* __VIDEO_SH_MOBILE_MERAM_H__ */
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/sh_mobile_lcdcfb.c | 18 +++++++----------- 1 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 7a0b301..671685d 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c @@ -857,7 +857,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) * re-initialize them. */ if (ch->meram) { - mdev->ops->meram_unregister(mdev, ch->meram); + sh_mobile_meram_unregister(mdev, ch->meram); ch->meram = NULL; }
@@ -880,11 +880,11 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) break; }
- meram = mdev->ops->meram_register(mdev, ch->cfg->meram_cfg, + meram = sh_mobile_meram_register(mdev, ch->cfg->meram_cfg, ch->pitch, ch->yres, pixelformat, &ch->pitch); if (!IS_ERR(meram)) { - mdev->ops->meram_update(mdev, meram, + sh_mobile_meram_update(mdev, meram, ch->base_addr_y, ch->base_addr_c, &ch->base_addr_y, &ch->base_addr_c); ch->meram = meram; @@ -1070,14 +1070,10 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, base_addr_c += var->xoffset; }
- if (ch->meram) { - struct sh_mobile_meram_info *mdev; - - mdev = priv->meram_dev; - mdev->ops->meram_update(mdev, ch->meram, - base_addr_y, base_addr_c, - &base_addr_y, &base_addr_c); - } + if (ch->meram) + sh_mobile_meram_update(priv->meram_dev, ch->meram, + base_addr_y, base_addr_c, + &base_addr_y, &base_addr_c);
ch->base_addr_y = base_addr_y; ch->base_addr_c = base_addr_c;
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- include/drm/drm_fourcc.h | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/include/drm/drm_fourcc.h b/include/drm/drm_fourcc.h index bdf0152..fac7235 100644 --- a/include/drm/drm_fourcc.h +++ b/include/drm/drm_fourcc.h @@ -106,6 +106,8 @@ #define DRM_FORMAT_NV21 fourcc_code('N', 'V', '2', '1') /* 2x2 subsampled Cb:Cr plane */ #define DRM_FORMAT_NV16 fourcc_code('N', 'V', '1', '6') /* 2x1 subsampled Cr:Cb plane */ #define DRM_FORMAT_NV61 fourcc_code('N', 'V', '6', '1') /* 2x1 subsampled Cb:Cr plane */ +#define DRM_FORMAT_NV24 fourcc_code('N', 'V', '2', '4') /* non-subsampled Cr:Cb plane */ +#define DRM_FORMAT_NV42 fourcc_code('N', 'V', '4', '2') /* non-subsampled Cb:Cr plane */
/* 2 non contiguous plane YCbCr */ #define DRM_FORMAT_NV12M fourcc_code('N', 'M', '1', '2') /* 2x2 subsampled Cr:Cb plane */
On Wed, May 30, 2012 at 02:32:58PM +0200, Laurent Pinchart wrote:
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
include/drm/drm_fourcc.h | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/include/drm/drm_fourcc.h b/include/drm/drm_fourcc.h index bdf0152..fac7235 100644 --- a/include/drm/drm_fourcc.h +++ b/include/drm/drm_fourcc.h @@ -106,6 +106,8 @@ #define DRM_FORMAT_NV21 fourcc_code('N', 'V', '2', '1') /* 2x2 subsampled Cb:Cr plane */ #define DRM_FORMAT_NV16 fourcc_code('N', 'V', '1', '6') /* 2x1 subsampled Cr:Cb plane */ #define DRM_FORMAT_NV61 fourcc_code('N', 'V', '6', '1') /* 2x1 subsampled Cb:Cr plane */ +#define DRM_FORMAT_NV24 fourcc_code('N', 'V', '2', '4') /* non-subsampled Cr:Cb plane */ +#define DRM_FORMAT_NV42 fourcc_code('N', 'V', '4', '2') /* non-subsampled Cb:Cr plane */
If you want these to reach the driver you need to add them to format_check().
Also you should update drm_format_num_planes() and drm_format_plane_cpp() appropriately.
Hi Ville,
Thank you for the review.
On Wednesday 30 May 2012 16:09:25 Ville Syrjälä wrote:
On Wed, May 30, 2012 at 02:32:58PM +0200, Laurent Pinchart wrote:
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
include/drm/drm_fourcc.h | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/include/drm/drm_fourcc.h b/include/drm/drm_fourcc.h index bdf0152..fac7235 100644 --- a/include/drm/drm_fourcc.h +++ b/include/drm/drm_fourcc.h @@ -106,6 +106,8 @@
#define DRM_FORMAT_NV21 fourcc_code('N', 'V', '2', '1') /* 2x2 subsampled Cb:Cr plane */ #define DRM_FORMAT_NV16 fourcc_code('N',
'V',
'1', '6') /* 2x1 subsampled Cr:Cb plane */ #define DRM_FORMAT_NV61 fourcc_code('N', 'V', '6', '1') /* 2x1 subsampled
Cb:Cr
plane */> +#define DRM_FORMAT_NV24 fourcc_code('N', 'V', '2', '4') /* non-subsampled Cr:Cb plane */ +#define DRM_FORMAT_NV42
fourcc_code('N',
'V', '4', '2') /* non-subsampled Cb:Cr plane */
If you want these to reach the driver you need to add them to format_check().
Oops, my bad, indeed.
Also you should update drm_format_num_planes() and drm_format_plane_cpp() appropriately.
Will do.
I'm a bit puzzled by drm_format_plane_cpp(). I would have expected the return value to be 1 for NV12/21 and NV16/61 formats (2 U/V components, but 1/2 horizontal subsampling). Is that a bug, or am I missing something ?
On Wed, May 30, 2012 at 03:20:09PM +0200, Laurent Pinchart wrote:
Hi Ville,
Thank you for the review.
On Wednesday 30 May 2012 16:09:25 Ville Syrjälä wrote:
On Wed, May 30, 2012 at 02:32:58PM +0200, Laurent Pinchart wrote:
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
include/drm/drm_fourcc.h | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/include/drm/drm_fourcc.h b/include/drm/drm_fourcc.h index bdf0152..fac7235 100644 --- a/include/drm/drm_fourcc.h +++ b/include/drm/drm_fourcc.h @@ -106,6 +106,8 @@
#define DRM_FORMAT_NV21 fourcc_code('N', 'V', '2', '1') /* 2x2 subsampled Cb:Cr plane */ #define DRM_FORMAT_NV16 fourcc_code('N',
'V',
'1', '6') /* 2x1 subsampled Cr:Cb plane */ #define DRM_FORMAT_NV61 fourcc_code('N', 'V', '6', '1') /* 2x1 subsampled
Cb:Cr
plane */> +#define DRM_FORMAT_NV24 fourcc_code('N', 'V', '2', '4') /* non-subsampled Cr:Cb plane */ +#define DRM_FORMAT_NV42
fourcc_code('N',
'V', '4', '2') /* non-subsampled Cb:Cr plane */
If you want these to reach the driver you need to add them to format_check().
Oops, my bad, indeed.
Also you should update drm_format_num_planes() and drm_format_plane_cpp() appropriately.
Will do.
I'm a bit puzzled by drm_format_plane_cpp(). I would have expected the return value to be 1 for NV12/21 and NV16/61 formats (2 U/V components, but 1/2 horizontal subsampling). Is that a bug, or am I missing something ?
The way I used it originally was to calculate the minimum stride for a plane. So the formula was something like this: min_stride = width / plane_horiz_subsampling * plane_cpp
I guess you could also call it pixel stride (as opposed to line stride). That is when you're walking the Cb (or Cr) samples you need to step two bytes to the get to the next sample.
Or if you just think about the chroma plane as just another packed pixel format then it also makes sense to have cpp=2.
YUYV and co. are more problematic though since you have the subsampled and non-subsampled stuff in the same plane. There you could argue that both 2 and 4 are sensible values. I used 2 there since it meant that I didn't have to add special cases to the minimum stride calculations.
There might be a need to add drm_format_macropixel_size() or some such function in the future which would return 4 for YUYV. Especially if someone wants to add funky formats such as Y41P.
Hi Ville,
On Wednesday 30 May 2012 17:05:10 Ville Syrjälä wrote:
On Wed, May 30, 2012 at 03:20:09PM +0200, Laurent Pinchart wrote:
On Wednesday 30 May 2012 16:09:25 Ville Syrjälä wrote:
On Wed, May 30, 2012 at 02:32:58PM +0200, Laurent Pinchart wrote:
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
include/drm/drm_fourcc.h | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/include/drm/drm_fourcc.h b/include/drm/drm_fourcc.h index bdf0152..fac7235 100644 --- a/include/drm/drm_fourcc.h +++ b/include/drm/drm_fourcc.h @@ -106,6 +106,8 @@
#define DRM_FORMAT_NV21 fourcc_code('N', 'V', '2', '1') /* 2x2 subsampled Cb:Cr plane */ #define DRM_FORMAT_NV16 fourcc_code('N', 'V', '1', '6') /* 2x1 subsampled Cr:Cb plane */ #define DRM_FORMAT_NV61 fourcc_code('N', 'V', '6', '1') /* 2x1 subsampled Cb:Cr plane */ +#define DRM_FORMAT_NV24 fourcc_code('N', 'V', '2', '4') /* non-subsampled Cr:Cb plane */ +#define DRM_FORMAT_NV42 fourcc_code('N', 'V', '4', '2') /* non-subsampled Cb:Cr plane */
If you want these to reach the driver you need to add them to format_check().
Oops, my bad, indeed.
Also you should update drm_format_num_planes() and drm_format_plane_cpp() appropriately.
Will do.
I'm a bit puzzled by drm_format_plane_cpp(). I would have expected the return value to be 1 for NV12/21 and NV16/61 formats (2 U/V components, but 1/2 horizontal subsampling). Is that a bug, or am I missing something ?
The way I used it originally was to calculate the minimum stride for a plane. So the formula was something like this: min_stride = width / plane_horiz_subsampling * plane_cpp
I had missed the division by the horizontal subsampling factor in framebuffer_check(). It makes sense now.
I guess you could also call it pixel stride (as opposed to line stride). That is when you're walking the Cb (or Cr) samples you need to step two bytes to the get to the next sample.
Or if you just think about the chroma plane as just another packed pixel format then it also makes sense to have cpp=2.
YUYV and co. are more problematic though since you have the subsampled and non-subsampled stuff in the same plane. There you could argue that both 2 and 4 are sensible values. I used 2 there since it meant that I didn't have to add special cases to the minimum stride calculations.
There might be a need to add drm_format_macropixel_size() or some such function in the future which would return 4 for YUYV. Especially if someone wants to add funky formats such as Y41P.
The SH Mobile LCD controller (LCDC) DRM driver supports the main graphics plane in RGB and YUV formats, as well as the overlay planes (in alpha-blending mode only).
Only flat panel outputs using the parallel interface are supported. Support for SYS panels, HDMI and DSI is currently not implemented.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/shmobile/Kconfig | 8 + drivers/gpu/drm/shmobile/Makefile | 8 + drivers/gpu/drm/shmobile/shmob_drm_backlight.c | 89 +++ drivers/gpu/drm/shmobile/shmob_drm_backlight.h | 23 + drivers/gpu/drm/shmobile/shmob_drm_crtc.c | 746 ++++++++++++++++++++++++ drivers/gpu/drm/shmobile/shmob_drm_crtc.h | 59 ++ drivers/gpu/drm/shmobile/shmob_drm_drv.c | 365 ++++++++++++ drivers/gpu/drm/shmobile/shmob_drm_drv.h | 52 ++ drivers/gpu/drm/shmobile/shmob_drm_gem.c | 195 ++++++ drivers/gpu/drm/shmobile/shmob_drm_gem.h | 46 ++ drivers/gpu/drm/shmobile/shmob_drm_kms.c | 211 +++++++ drivers/gpu/drm/shmobile/shmob_drm_kms.h | 44 ++ drivers/gpu/drm/shmobile/shmob_drm_plane.c | 240 ++++++++ drivers/gpu/drm/shmobile/shmob_drm_plane.h | 22 + drivers/gpu/drm/shmobile/shmob_drm_regs.h | 304 ++++++++++ include/drm/shmob_drm.h | 99 ++++ 18 files changed, 2514 insertions(+), 0 deletions(-) create mode 100644 drivers/gpu/drm/shmobile/Kconfig create mode 100644 drivers/gpu/drm/shmobile/Makefile create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_backlight.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_backlight.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_crtc.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_crtc.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_drv.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_drv.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_gem.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_gem.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_kms.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_kms.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_plane.c create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_plane.h create mode 100644 drivers/gpu/drm/shmobile/shmob_drm_regs.h create mode 100644 include/drm/shmob_drm.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 23120c0..347d860 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -192,3 +192,5 @@ source "drivers/gpu/drm/ast/Kconfig" source "drivers/gpu/drm/mgag200/Kconfig"
source "drivers/gpu/drm/cirrus/Kconfig" + +source "drivers/gpu/drm/shmobile/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index f65f65e..0db4c0a 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -45,4 +45,5 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/ obj-$(CONFIG_DRM_GMA500) += gma500/ obj-$(CONFIG_DRM_UDL) += udl/ obj-$(CONFIG_DRM_AST) += ast/ +obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-y += i2c/ diff --git a/drivers/gpu/drm/shmobile/Kconfig b/drivers/gpu/drm/shmobile/Kconfig new file mode 100644 index 0000000..d3fb8df --- /dev/null +++ b/drivers/gpu/drm/shmobile/Kconfig @@ -0,0 +1,8 @@ +config DRM_SHMOBILE + tristate "DRM Support for SH Mobile" + depends on DRM && (SUPERH || ARCH_SHMOBILE) + select DRM_KMS_HELPER + help + Choose this option if you have an SH Mobile chipset. + If M is selected the module will be called shmob-drm. + diff --git a/drivers/gpu/drm/shmobile/Makefile b/drivers/gpu/drm/shmobile/Makefile new file mode 100644 index 0000000..6ecf33d --- /dev/null +++ b/drivers/gpu/drm/shmobile/Makefile @@ -0,0 +1,8 @@ +shmob-drm-y := shmob_drm_backlight.o \ + shmob_drm_crtc.o \ + shmob_drm_drv.o \ + shmob_drm_gem.o \ + shmob_drm_kms.o \ + shmob_drm_plane.o + +obj-$(CONFIG_DRM_SHMOBILE) += shmob-drm.o diff --git a/drivers/gpu/drm/shmobile/shmob_drm_backlight.c b/drivers/gpu/drm/shmobile/shmob_drm_backlight.c new file mode 100644 index 0000000..4477a3b --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_backlight.c @@ -0,0 +1,89 @@ +/* + * shmob_drm_backlight.c -- SH Mobile DRM Backlight + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/backlight.h> + +#include "shmob_drm_backlight.h" +#include "shmob_drm_crtc.h" +#include "shmob_drm_drv.h" + +static int shmob_drm_backlight_update(struct backlight_device *bdev) +{ + struct shmob_drm_connector *scon = bl_get_data(bdev); + struct shmob_drm_device *sdev = scon->connector.dev->dev_private; + const struct shmob_drm_backlight_data *bdata = &sdev->pdata->backlight; + int brightness = bdev->props.brightness; + + if (bdev->props.power != FB_BLANK_UNBLANK || + bdev->props.state & BL_CORE_SUSPENDED) + brightness = 0; + + return bdata->set_brightness(brightness); +} + +static int shmob_drm_backlight_get_brightness(struct backlight_device *bdev) +{ + struct shmob_drm_connector *scon = bl_get_data(bdev); + struct shmob_drm_device *sdev = scon->connector.dev->dev_private; + const struct shmob_drm_backlight_data *bdata = &sdev->pdata->backlight; + + return bdata->get_brightness(); +} + +static const struct backlight_ops shmob_drm_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = shmob_drm_backlight_update, + .get_brightness = shmob_drm_backlight_get_brightness, +}; + +void shmob_drm_backlight_dpms(struct shmob_drm_connector *scon, int mode) +{ + if (scon->backlight == NULL) + return; + + scon->backlight->props.power = mode == DRM_MODE_DPMS_ON + ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; + backlight_update_status(scon->backlight); +} + +int shmob_drm_backlight_init(struct shmob_drm_connector *scon) +{ + struct shmob_drm_device *sdev = scon->connector.dev->dev_private; + const struct shmob_drm_backlight_data *bdata = &sdev->pdata->backlight; + struct drm_connector *connector = &scon->connector; + struct drm_device *dev = connector->dev; + struct backlight_device *backlight; + + if (!bdata->max_brightness) + return 0; + + backlight = backlight_device_register(bdata->name, dev->dev, scon, + &shmob_drm_backlight_ops, NULL); + if (IS_ERR(backlight)) { + dev_err(dev->dev, "unable to register backlight device: %ld\n", + PTR_ERR(backlight)); + return PTR_ERR(backlight); + } + + backlight->props.max_brightness = bdata->max_brightness; + backlight->props.brightness = bdata->max_brightness; + backlight_update_status(backlight); + + scon->backlight = backlight; + return 0; +} + +void shmob_drm_backlight_exit(struct shmob_drm_connector *scon) +{ + backlight_device_unregister(scon->backlight); +} diff --git a/drivers/gpu/drm/shmobile/shmob_drm_backlight.h b/drivers/gpu/drm/shmobile/shmob_drm_backlight.h new file mode 100644 index 0000000..9477595 --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_backlight.h @@ -0,0 +1,23 @@ +/* + * shmob_drm_backlight.h -- SH Mobile DRM Backlight + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SHMOB_DRM_BACKLIGHT_H__ +#define __SHMOB_DRM_BACKLIGHT_H__ + +struct shmob_drm_connector; + +void shmob_drm_backlight_dpms(struct shmob_drm_connector *scon, int mode); +int shmob_drm_backlight_init(struct shmob_drm_connector *scon); +void shmob_drm_backlight_exit(struct shmob_drm_connector *scon); + +#endif /* __SHMOB_DRM_BACKLIGHT_H__ */ diff --git a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c new file mode 100644 index 0000000..ca6ea3d --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c @@ -0,0 +1,746 @@ +/* + * shmob_drm_crtc.c -- SH Mobile DRM CRTCs + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/backlight.h> +#include <linux/clk.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> + +#include <video/sh_mobile_meram.h> + +#include "shmob_drm_backlight.h" +#include "shmob_drm_crtc.h" +#include "shmob_drm_drv.h" +#include "shmob_drm_kms.h" +#include "shmob_drm_plane.h" +#include "shmob_drm_regs.h" + +/* + * TODO: panel support + */ + +/* ----------------------------------------------------------------------------- + * Clock management + */ + +static void shmob_drm_clk_on(struct shmob_drm_device *sdev) +{ + if (sdev->clock) + clk_enable(sdev->clock); +#if 0 + if (sdev->meram_dev && sdev->meram_dev->pdev) + pm_runtime_get_sync(&sdev->meram_dev->pdev->dev); +#endif +} + +static void shmob_drm_clk_off(struct shmob_drm_device *sdev) +{ +#if 0 + if (sdev->meram_dev && sdev->meram_dev->pdev) + pm_runtime_put_sync(&sdev->meram_dev->pdev->dev); +#endif + if (sdev->clock) + clk_disable(sdev->clock); +} + +/* ----------------------------------------------------------------------------- + * CRTC + */ + +static void shmob_drm_crtc_setup_geometry(struct shmob_drm_crtc *scrtc) +{ + struct drm_crtc *crtc = &scrtc->crtc; + struct shmob_drm_device *sdev = crtc->dev->dev_private; + const struct shmob_drm_interface_data *idata = &sdev->pdata->iface; + const struct drm_display_mode *mode = &crtc->mode; + u32 value; + + value = sdev->ldmt1r + | ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : LDMT1R_VPOL) + | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : LDMT1R_HPOL) + | ((idata->flags & SHMOB_DRM_IFACE_FL_DWPOL) ? LDMT1R_DWPOL : 0) + | ((idata->flags & SHMOB_DRM_IFACE_FL_DIPOL) ? LDMT1R_DIPOL : 0) + | ((idata->flags & SHMOB_DRM_IFACE_FL_DAPOL) ? LDMT1R_DAPOL : 0) + | ((idata->flags & SHMOB_DRM_IFACE_FL_HSCNT) ? LDMT1R_HSCNT : 0) + | ((idata->flags & SHMOB_DRM_IFACE_FL_DWCNT) ? LDMT1R_DWCNT : 0); + lcdc_write(sdev, LDMT1R, value); + + if (idata->interface >= SHMOB_DRM_IFACE_SYS8A && + idata->interface <= SHMOB_DRM_IFACE_SYS24) { + /* Setup SYS bus. */ + value = (idata->sys.cs_setup << LDMT2R_CSUP_SHIFT) + | (idata->sys.vsync_active_high ? LDMT2R_RSV : 0) + | (idata->sys.vsync_dir_input ? LDMT2R_VSEL : 0) + | (idata->sys.write_setup << LDMT2R_WCSC_SHIFT) + | (idata->sys.write_cycle << LDMT2R_WCEC_SHIFT) + | (idata->sys.write_strobe << LDMT2R_WCLW_SHIFT); + lcdc_write(sdev, LDMT2R, value); + + value = (idata->sys.read_latch << LDMT3R_RDLC_SHIFT) + | (idata->sys.read_setup << LDMT3R_RCSC_SHIFT) + | (idata->sys.read_cycle << LDMT3R_RCEC_SHIFT) + | (idata->sys.read_strobe << LDMT3R_RCLW_SHIFT); + lcdc_write(sdev, LDMT3R, value); + } + + value = ((mode->hdisplay / 8) << 16) /* HDCN */ + | (mode->htotal / 8); /* HTCN */ + lcdc_write(sdev, LDHCNR, value); + + value = (((mode->hsync_end - mode->hsync_start) / 8) << 16) /* HSYNW */ + | (mode->hsync_start / 8); /* HSYNP */ + lcdc_write(sdev, LDHSYNR, value); + + value = ((mode->hdisplay & 7) << 24) | ((mode->htotal & 7) << 16) + | (((mode->hsync_end - mode->hsync_start) & 7) << 8) + | (mode->hsync_start & 7); + lcdc_write(sdev, LDHAJR, value); + + value = ((mode->vdisplay) << 16) /* VDLN */ + | mode->vtotal; /* VTLN */ + lcdc_write(sdev, LDVLNR, value); + + value = ((mode->vsync_end - mode->vsync_start) << 16) /* VSYNW */ + | mode->vsync_start; /* VSYNP */ + lcdc_write(sdev, LDVSYNR, value); +} + +static void shmob_drm_crtc_start_stop(struct shmob_drm_crtc *scrtc, bool start) +{ + struct shmob_drm_device *sdev = scrtc->crtc.dev->dev_private; + u32 value; + + value = lcdc_read(sdev, LDCNT2R); + if (start) + lcdc_write(sdev, LDCNT2R, value | LDCNT2R_DO); + else + lcdc_write(sdev, LDCNT2R, value & ~LDCNT2R_DO); + + /* Wait until power is applied/stopped. */ + while (1) { + value = lcdc_read(sdev, LDPMR) & LDPMR_LPS; + if ((start && value) || (!start && !value)) + break; + + cpu_relax(); + } + + if (!start) { + /* Stop the dot clock. */ + lcdc_write(sdev, LDDCKSTPR, LDDCKSTPR_DCKSTP); + } +} + +/* + * shmob_drm_crtc_start - Configure and start the LCDC + * @scrtc: the SH Mobile CRTC + * + * Configure and start the LCDC device. External devices (clocks, MERAM, panels, + * ...) are not touched by this function. + */ +static void shmob_drm_crtc_start(struct shmob_drm_crtc *scrtc) +{ + struct drm_crtc *crtc = &scrtc->crtc; + struct shmob_drm_device *sdev = crtc->dev->dev_private; + struct shmob_drm_framebuffer *sfb = to_shmob_framebuffer(crtc->fb); + const struct shmob_drm_interface_data *idata = &sdev->pdata->iface; + struct drm_device *dev = sdev->ddev; + struct drm_plane *plane; + u32 value; + + if (scrtc->started) + return; + + /* Enable clocks before accessing the hardware. */ + shmob_drm_clk_on(sdev); + + /* Reset and enable the LCDC. */ + lcdc_write(sdev, LDCNT2R, lcdc_read(sdev, LDCNT2R) | LDCNT2R_BR); + lcdc_wait_bit(sdev, LDCNT2R, LDCNT2R_BR, 0); + lcdc_write(sdev, LDCNT2R, LDCNT2R_ME); + + /* Stop the LCDC first and disable all interrupts. */ + shmob_drm_crtc_start_stop(scrtc, false); + lcdc_write(sdev, LDINTR, 0); + + /* Configure power supply, dot clocks and start them. */ + lcdc_write(sdev, LDPMR, 0); + + value = sdev->lddckr; + if (idata->clk_div) { + /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider + * denominator. + */ + lcdc_write(sdev, LDDCKPAT1R, 0); + lcdc_write(sdev, LDDCKPAT2R, (1 << (idata->clk_div / 2)) - 1); + + if (idata->clk_div == 1) + value |= LDDCKR_MOSEL; + else + value |= idata->clk_div; + } + + lcdc_write(sdev, LDDCKR, value); + lcdc_write(sdev, LDDCKSTPR, 0); + lcdc_wait_bit(sdev, LDDCKSTPR, ~0, 0); + + /* TODO: Setup SYS panel */ + + /* Setup geometry, format, frame buffer memory and operation mode. */ + shmob_drm_crtc_setup_geometry(scrtc); + + /* TODO: Handle YUV colorspaces. Hardcode REC709 for now. */ + lcdc_write(sdev, LDDFR, sfb->format->lddfr | LDDFR_CF1); + lcdc_write(sdev, LDMLSR, scrtc->line_size); + lcdc_write(sdev, LDSA1R, scrtc->dma[0]); + if (sfb->format->yuv) + lcdc_write(sdev, LDSA2R, scrtc->dma[1]); + lcdc_write(sdev, LDSM1R, 0); + + /* Word and long word swap. */ + switch (sfb->format->fourcc) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV42: + value = LDDDSR_LS | LDDDSR_WS; + break; + case DRM_FORMAT_RGB888: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV24: + value = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS; + break; + case DRM_FORMAT_ARGB8888: + default: + value = LDDDSR_LS; + break; + } + lcdc_write(sdev, LDDDSR, value); + + /* Setup planes. */ + list_for_each_entry(plane, &dev->mode_config.plane_list, head) { + if (plane->crtc == crtc) + shmob_drm_plane_setup(plane); + } + + /* Enable the display output. */ + lcdc_write(sdev, LDCNT1R, LDCNT1R_DE); + + shmob_drm_crtc_start_stop(scrtc, true); + + scrtc->started = true; +} + +static void shmob_drm_crtc_stop(struct shmob_drm_crtc *scrtc) +{ + struct drm_crtc *crtc = &scrtc->crtc; + struct shmob_drm_device *sdev = crtc->dev->dev_private; + + if (!scrtc->started) + return; + + /* Disable the MERAM. */ + if (scrtc->meram) { + sh_mobile_meram_unregister(sdev->meram, scrtc->meram); + scrtc->meram = NULL; + } + + /* Stop the LCDC. */ + shmob_drm_crtc_start_stop(scrtc, false); + + /* Disable the display output. */ + lcdc_write(sdev, LDCNT1R, 0); + + /* Stop clocks. */ + shmob_drm_clk_off(sdev); + + scrtc->started = false; +} + +void shmob_drm_crtc_suspend(struct shmob_drm_crtc *scrtc) +{ + shmob_drm_crtc_stop(scrtc); +} + +void shmob_drm_crtc_resume(struct shmob_drm_crtc *scrtc) +{ + if (scrtc->dpms != DRM_MODE_DPMS_ON) + return; + + shmob_drm_crtc_start(scrtc); +} + +static void shmob_drm_crtc_compute_base(struct shmob_drm_crtc *scrtc, + int x, int y) +{ + struct drm_crtc *crtc = &scrtc->crtc; + struct shmob_drm_device *sdev = crtc->dev->dev_private; + struct shmob_drm_framebuffer *sfb = to_shmob_framebuffer(crtc->fb); + unsigned int bpp; + + bpp = sfb->format->yuv ? 8 : sfb->format->bpp; + scrtc->dma[0] = sfb->sobj[0]->dma + y * sfb->base.pitches[0] + + x * bpp / 8; + + if (sfb->format->yuv) { + bpp = sfb->format->bpp - 8; + scrtc->dma[1] = sfb->sobj[1]->dma + + y * sfb->base.pitches[0] * bpp / 8 + + x * (bpp == 16 ? 2 : 1); + } + + if (scrtc->meram) + sh_mobile_meram_update(sdev->meram, scrtc->meram, + scrtc->dma[0], scrtc->dma[1], + &scrtc->dma[0], &scrtc->dma[1]); +} + +static void shmob_drm_crtc_update_base(struct shmob_drm_crtc *scrtc) +{ + struct drm_crtc *crtc = &scrtc->crtc; + struct shmob_drm_framebuffer *sfb = to_shmob_framebuffer(crtc->fb); + struct shmob_drm_device *sdev = crtc->dev->dev_private; + + shmob_drm_crtc_compute_base(scrtc, crtc->x, crtc->y); + + lcdc_write_mirror(sdev, LDSA1R, scrtc->dma[0]); + if (sfb->format->yuv) + lcdc_write_mirror(sdev, LDSA2R, scrtc->dma[1]); + + lcdc_write(sdev, LDRCNTR, lcdc_read(sdev, LDRCNTR) ^ LDRCNTR_MRS); +} + +#define to_shmob_crtc(c) container_of(c, struct shmob_drm_crtc, crtc) + +static void shmob_drm_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); + + if (scrtc->dpms == mode) + return; + + if (mode == DRM_MODE_DPMS_ON) + shmob_drm_crtc_start(scrtc); + else + shmob_drm_crtc_stop(scrtc); + + scrtc->dpms = mode; +} + +static bool shmob_drm_crtc_mode_fixup(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void shmob_drm_crtc_mode_prepare(struct drm_crtc *crtc) +{ + shmob_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static int shmob_drm_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, + struct drm_framebuffer *old_fb) +{ + struct shmob_drm_framebuffer *sfb = to_shmob_framebuffer(crtc->fb); + struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); + struct shmob_drm_device *sdev = crtc->dev->dev_private; + const struct sh_mobile_meram_cfg *mdata = sdev->pdata->meram; + void *meram; + + scrtc->line_size = sfb->base.pitches[0]; + + /* Enable MERAM if configured. We need to de-init configured ICBs before + * we can re-initialize them. + */ + if (scrtc->meram) { + sh_mobile_meram_unregister(sdev->meram, scrtc->meram); + scrtc->meram = NULL; + } + + meram = sh_mobile_meram_register(sdev->meram, mdata, + sfb->base.pitches[0], + adjusted_mode->vdisplay, + sfb->format->meram, &scrtc->line_size); + if (!IS_ERR(meram)) + scrtc->meram = meram; + + shmob_drm_crtc_compute_base(scrtc, x, y); + + return 0; +} + +static void shmob_drm_crtc_mode_commit(struct drm_crtc *crtc) +{ + shmob_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static int shmob_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + shmob_drm_crtc_update_base(to_shmob_crtc(crtc)); + + return 0; +} + +static const struct drm_crtc_helper_funcs crtc_helper_funcs = { + .dpms = shmob_drm_crtc_dpms, + .mode_fixup = shmob_drm_crtc_mode_fixup, + .prepare = shmob_drm_crtc_mode_prepare, + .commit = shmob_drm_crtc_mode_commit, + .mode_set = shmob_drm_crtc_mode_set, + .mode_set_base = shmob_drm_crtc_mode_set_base, +}; + +static void shmob_drm_crtc_destroy(struct drm_crtc *crtc) +{ + drm_crtc_cleanup(crtc); +} + +void shmob_drm_crtc_cancel_page_flip(struct shmob_drm_crtc *scrtc, + struct drm_file *file) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = scrtc->crtc.dev; + unsigned long flags; + + /* Destroy the pending vertical blanking event associated with the + * pending page flip, if any, and disable vertical blanking interrupts. + */ + spin_lock_irqsave(&dev->event_lock, flags); + event = scrtc->event; + if (event && event->base.file_priv == file) { + scrtc->event = NULL; + event->base.destroy(&event->base); + drm_vblank_put(dev, 0); + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +void shmob_drm_crtc_finish_page_flip(struct shmob_drm_crtc *scrtc) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = scrtc->crtc.dev; + struct timeval vblanktime; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = scrtc->event; + scrtc->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + if (event == NULL) + return; + + event->event.sequence = drm_vblank_count_and_time(dev, 0, &vblanktime); + event->event.tv_sec = vblanktime.tv_sec; + event->event.tv_usec = vblanktime.tv_usec; + + spin_lock_irqsave(&dev->event_lock, flags); + list_add_tail(&event->base.link, &event->base.file_priv->event_list); + wake_up_interruptible(&event->base.file_priv->event_wait); + spin_unlock_irqrestore(&dev->event_lock, flags); + + drm_vblank_put(dev, 0); +} + +static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); + struct drm_device *dev = scrtc->crtc.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (scrtc->event != NULL) { + spin_unlock_irqrestore(&dev->event_lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&dev->event_lock, flags); + + crtc->fb = fb; + shmob_drm_crtc_update_base(scrtc); + + if (event) { + event->pipe = 0; + spin_lock_irqsave(&dev->event_lock, flags); + scrtc->event = event; + spin_unlock_irqrestore(&dev->event_lock, flags); + drm_vblank_get(dev, 0); + } + + return 0; +} + +static const struct drm_crtc_funcs crtc_funcs = { + .destroy = shmob_drm_crtc_destroy, + .set_config = drm_crtc_helper_set_config, + .page_flip = shmob_drm_crtc_page_flip, +}; + +int shmob_drm_crtc_create(struct shmob_drm_device *sdev) +{ + struct drm_crtc *crtc = &sdev->crtc.crtc; + int ret; + + sdev->crtc.dpms = DRM_MODE_DPMS_OFF; + + ret = drm_crtc_init(sdev->ddev, crtc, &crtc_funcs); + if (ret < 0) + return ret; + + drm_crtc_helper_add(crtc, &crtc_helper_funcs); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Encoder + */ + +#define to_shmob_encoder(e) \ + container_of(e, struct shmob_drm_encoder, encoder) + +static void shmob_drm_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct shmob_drm_encoder *senc = to_shmob_encoder(encoder); + struct shmob_drm_device *sdev = encoder->dev->dev_private; + struct shmob_drm_connector *scon = &sdev->connector; + + if (senc->dpms == mode) + return; + + shmob_drm_backlight_dpms(scon, mode); + + senc->dpms = mode; +} + +static bool shmob_drm_encoder_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct shmob_drm_device *sdev = dev->dev_private; + struct drm_connector *connector = &sdev->connector.connector; + const struct drm_display_mode *panel_mode; + + if (list_empty(&connector->modes)) { + dev_dbg(dev->dev, "mode_fixup: empty modes list\n"); + return false; + } + + /* The flat panel mode is fixed, just copy it to the adjusted mode. */ + panel_mode = list_first_entry(&connector->modes, + struct drm_display_mode, head); + drm_mode_copy(adjusted_mode, panel_mode); + + return true; +} + +static void shmob_drm_encoder_mode_prepare(struct drm_encoder *encoder) +{ + /* No-op, everything is handled in the CRTC code. */ +} + +static void shmob_drm_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* No-op, everything is handled in the CRTC code. */ +} + +static void shmob_drm_encoder_mode_commit(struct drm_encoder *encoder) +{ + /* No-op, everything is handled in the CRTC code. */ +} + +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { + .dpms = shmob_drm_encoder_dpms, + .mode_fixup = shmob_drm_encoder_mode_fixup, + .prepare = shmob_drm_encoder_mode_prepare, + .commit = shmob_drm_encoder_mode_commit, + .mode_set = shmob_drm_encoder_mode_set, +}; + +static void shmob_drm_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs encoder_funcs = { + .destroy = shmob_drm_encoder_destroy, +}; + +int shmob_drm_encoder_create(struct shmob_drm_device *sdev) +{ + struct drm_encoder *encoder = &sdev->encoder.encoder; + int ret; + + sdev->encoder.dpms = DRM_MODE_DPMS_OFF; + + encoder->possible_crtcs = 1; + + ret = drm_encoder_init(sdev->ddev, encoder, &encoder_funcs, + DRM_MODE_ENCODER_LVDS); + if (ret < 0) + return ret; + + drm_encoder_helper_add(encoder, &encoder_helper_funcs); + + return 0; +} + +void shmob_drm_crtc_enable_vblank(struct shmob_drm_device *sdev, bool enable) +{ + unsigned long flags; + u32 ldintr; + + /* Be careful not to acknowledge any pending interrupt. */ + spin_lock_irqsave(&sdev->irq_lock, flags); + ldintr = lcdc_read(sdev, LDINTR) | LDINTR_STATUS_MASK; + if (enable) + ldintr |= LDINTR_VEE; + else + ldintr &= ~LDINTR_VEE; + lcdc_write(sdev, LDINTR, ldintr); + spin_unlock_irqrestore(&sdev->irq_lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Connector + */ + +#define to_shmob_connector(c) \ + container_of(c, struct shmob_drm_connector, connector) + +static int shmob_drm_connector_get_modes(struct drm_connector *connector) +{ + struct shmob_drm_connector *scon = to_shmob_connector(connector); + struct shmob_drm_device *sdev = scon->connector.dev->dev_private; + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (mode == NULL) + return 0; + + mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; + mode->clock = sdev->pdata->panel.mode.clock; + mode->hdisplay = sdev->pdata->panel.mode.hdisplay; + mode->hsync_start = sdev->pdata->panel.mode.hsync_start; + mode->hsync_end = sdev->pdata->panel.mode.hsync_end; + mode->htotal = sdev->pdata->panel.mode.htotal; + mode->vdisplay = sdev->pdata->panel.mode.vdisplay; + mode->vsync_start = sdev->pdata->panel.mode.vsync_start; + mode->vsync_end = sdev->pdata->panel.mode.vsync_end; + mode->vtotal = sdev->pdata->panel.mode.vtotal; + mode->flags = sdev->pdata->panel.mode.flags; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = sdev->pdata->panel.width_mm; + connector->display_info.height_mm = sdev->pdata->panel.height_mm; + + return 1; +} + +static int shmob_drm_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static struct drm_encoder * +shmob_drm_connector_best_encoder(struct drm_connector *connector) +{ + struct shmob_drm_connector *scon = to_shmob_connector(connector); + + return scon->encoder; +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = shmob_drm_connector_get_modes, + .mode_valid = shmob_drm_connector_mode_valid, + .best_encoder = shmob_drm_connector_best_encoder, +}; + +static void shmob_drm_connector_destroy(struct drm_connector *connector) +{ + struct shmob_drm_connector *scon = to_shmob_connector(connector); + + shmob_drm_backlight_exit(scon); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); +} + +static enum drm_connector_status +shmob_drm_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_unknown; +} + +static const struct drm_connector_funcs connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = shmob_drm_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = shmob_drm_connector_destroy, +}; + +int shmob_drm_connector_create(struct shmob_drm_device *sdev, + struct drm_encoder *encoder) +{ + struct drm_connector *connector = &sdev->connector.connector; + int ret; + + sdev->connector.encoder = encoder; + + connector->display_info.width_mm = sdev->pdata->panel.width_mm; + connector->display_info.height_mm = sdev->pdata->panel.height_mm; + + ret = drm_connector_init(sdev->ddev, connector, &connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + if (ret < 0) + return ret; + + drm_connector_helper_add(connector, &connector_helper_funcs); + ret = drm_sysfs_connector_add(connector); + if (ret < 0) + goto err_cleanup; + + ret = shmob_drm_backlight_init(&sdev->connector); + if (ret < 0) + goto err_sysfs; + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret < 0) + goto err_backlight; + + connector->encoder = encoder; + + return 0; + +err_backlight: + shmob_drm_backlight_exit(&sdev->connector); +err_sysfs: + drm_sysfs_connector_remove(connector); +err_cleanup: + drm_connector_cleanup(connector); + return ret; +} diff --git a/drivers/gpu/drm/shmobile/shmob_drm_crtc.h b/drivers/gpu/drm/shmobile/shmob_drm_crtc.h new file mode 100644 index 0000000..d874ffc --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_crtc.h @@ -0,0 +1,59 @@ +/* + * shmob_drm_crtc.h -- SH Mobile DRM CRTCs + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SHMOB_DRM_CRTC_H__ +#define __SHMOB_DRM_CRTC_H__ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> + +struct backlight_device; +struct shmob_drm_device; + +struct shmob_drm_crtc { + struct drm_crtc crtc; + + struct drm_pending_vblank_event *event; + int dpms; + + void *meram; + unsigned long dma[2]; + unsigned int line_size; + bool started; +}; + +struct shmob_drm_encoder { + struct drm_encoder encoder; + int dpms; +}; + +struct shmob_drm_connector { + struct drm_connector connector; + struct drm_encoder *encoder; + + struct backlight_device *backlight; +}; + +int shmob_drm_crtc_create(struct shmob_drm_device *sdev); +void shmob_drm_crtc_enable_vblank(struct shmob_drm_device *sdev, bool enable); +void shmob_drm_crtc_cancel_page_flip(struct shmob_drm_crtc *scrtc, + struct drm_file *file); +void shmob_drm_crtc_finish_page_flip(struct shmob_drm_crtc *scrtc); +void shmob_drm_crtc_suspend(struct shmob_drm_crtc *scrtc); +void shmob_drm_crtc_resume(struct shmob_drm_crtc *scrtc); + +int shmob_drm_encoder_create(struct shmob_drm_device *sdev); +int shmob_drm_connector_create(struct shmob_drm_device *sdev, + struct drm_encoder *encoder); + +#endif /* __SHMOB_DRM_CRTC_H__ */ diff --git a/drivers/gpu/drm/shmobile/shmob_drm_drv.c b/drivers/gpu/drm/shmobile/shmob_drm_drv.c new file mode 100644 index 0000000..f86ac86 --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_drv.c @@ -0,0 +1,365 @@ +/* + * shmob_drm_drv.c -- SH Mobile DRM driver + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "shmob_drm_crtc.h" +#include "shmob_drm_drv.h" +#include "shmob_drm_gem.h" +#include "shmob_drm_kms.h" +#include "shmob_drm_plane.h" +#include "shmob_drm_regs.h" + +/* ----------------------------------------------------------------------------- + * Hardware initialization + */ + +static int __devinit shmob_drm_init_interface(struct shmob_drm_device *sdev) +{ + static const u32 ldmt1r[] = { + [SHMOB_DRM_IFACE_RGB8] = LDMT1R_MIFTYP_RGB8, + [SHMOB_DRM_IFACE_RGB9] = LDMT1R_MIFTYP_RGB9, + [SHMOB_DRM_IFACE_RGB12A] = LDMT1R_MIFTYP_RGB12A, + [SHMOB_DRM_IFACE_RGB12B] = LDMT1R_MIFTYP_RGB12B, + [SHMOB_DRM_IFACE_RGB16] = LDMT1R_MIFTYP_RGB16, + [SHMOB_DRM_IFACE_RGB18] = LDMT1R_MIFTYP_RGB18, + [SHMOB_DRM_IFACE_RGB24] = LDMT1R_MIFTYP_RGB24, + [SHMOB_DRM_IFACE_YUV422] = LDMT1R_MIFTYP_YCBCR, + [SHMOB_DRM_IFACE_SYS8A] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8A, + [SHMOB_DRM_IFACE_SYS8B] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8B, + [SHMOB_DRM_IFACE_SYS8C] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8C, + [SHMOB_DRM_IFACE_SYS8D] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8D, + [SHMOB_DRM_IFACE_SYS9] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS9, + [SHMOB_DRM_IFACE_SYS12] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS12, + [SHMOB_DRM_IFACE_SYS16A] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16A, + [SHMOB_DRM_IFACE_SYS16B] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16B, + [SHMOB_DRM_IFACE_SYS16C] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16C, + [SHMOB_DRM_IFACE_SYS18] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS18, + [SHMOB_DRM_IFACE_SYS24] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS24, + }; + + if (sdev->pdata->iface.interface >= ARRAY_SIZE(ldmt1r)) { + dev_err(sdev->dev, "invalid interface type %u\n", + sdev->pdata->iface.interface); + return -EINVAL; + } + + sdev->ldmt1r = ldmt1r[sdev->pdata->iface.interface]; + return 0; +} + +static int __devinit shmob_drm_setup_clocks(struct shmob_drm_device *sdev, + enum shmob_drm_clk_source clksrc) +{ + struct clk *clk; + char *clkname; + + switch (clksrc) { + case SHMOB_DRM_CLK_BUS: + clkname = "bus_clk"; + sdev->lddckr = LDDCKR_ICKSEL_BUS; + break; + case SHMOB_DRM_CLK_PERIPHERAL: + clkname = "peripheral_clk"; + sdev->lddckr = LDDCKR_ICKSEL_MIPI; + break; + case SHMOB_DRM_CLK_EXTERNAL: + clkname = NULL; + sdev->lddckr = LDDCKR_ICKSEL_HDMI; + break; + default: + return -EINVAL; + } + + clk = clk_get(sdev->dev, clkname); + if (IS_ERR(clk)) { + dev_err(sdev->dev, "cannot get dot clock %s\n", clkname); + return PTR_ERR(clk); + } + + sdev->clock = clk; + return 0; +} + +/* ----------------------------------------------------------------------------- + * DRM operations + */ + +static int shmob_drm_unload(struct drm_device *dev) +{ + struct shmob_drm_device *sdev = dev->dev_private; + + drm_kms_helper_poll_fini(dev); + drm_vblank_cleanup(dev); + drm_irq_uninstall(dev); + + if (sdev->clock) + clk_put(sdev->clock); + + if (sdev->mmio) + iounmap(sdev->mmio); + + dev->dev_private = NULL; + kfree(sdev); + + return 0; +} + +static int shmob_drm_load(struct drm_device *dev, unsigned long flags) +{ + struct shmob_drm_platform_data *pdata = dev->dev->platform_data; + struct platform_device *pdev = dev->platformdev; + struct shmob_drm_device *sdev; + struct resource *res; + unsigned int i; + int ret; + + if (pdata == NULL) { + dev_err(dev->dev, "no platform data\n"); + return -EINVAL; + } + + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (sdev == NULL) { + dev_err(dev->dev, "failed to allocate private data\n"); + return -ENOMEM; + } + + sdev->dev = &pdev->dev; + sdev->pdata = pdata; + spin_lock_init(&sdev->irq_lock); + + sdev->ddev = dev; + dev->dev_private = sdev; + + /* I/O resources and clocks */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get memory resource\n"); + ret = -EINVAL; + goto done; + } + + sdev->mmio = ioremap_nocache(res->start, resource_size(res)); + if (sdev->mmio == NULL) { + dev_err(&pdev->dev, "failed to remap memory resource\n"); + ret = -ENOMEM; + goto done; + } + + ret = shmob_drm_setup_clocks(sdev, pdata->clk_source); + if (ret < 0) + goto done; + + ret = shmob_drm_init_interface(sdev); + if (ret < 0) + goto done; + + ret = shmob_drm_modeset_init(sdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to initialize mode setting\n"); + goto done; + } + + for (i = 0; i < 4; ++i) { + ret = shmob_drm_plane_create(sdev, i); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create plane %u\n", i); + goto done; + } + } + + ret = drm_vblank_init(dev, 1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to initialize vblank\n"); + goto done; + } + + ret = drm_irq_install(dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to install IRQ handler\n"); + goto done; + } + +done: + if (ret) + shmob_drm_unload(dev); + + return ret; +} + +static void shmob_drm_preclose(struct drm_device *dev, struct drm_file *file) +{ + struct shmob_drm_device *sdev = dev->dev_private; + + shmob_drm_crtc_cancel_page_flip(&sdev->crtc, file); +} + +static irqreturn_t shmob_drm_irq(int irq, void *arg) +{ + struct drm_device *dev = arg; + struct shmob_drm_device *sdev = dev->dev_private; + unsigned long flags; + u32 status; + + /* Acknowledge interrupts. Putting interrupt enable and interrupt flag + * bits in the same register is really brain-dead design and requires + * taking a spinlock. + */ + spin_lock_irqsave(&sdev->irq_lock, flags); + status = lcdc_read(sdev, LDINTR); + lcdc_write(sdev, LDINTR, status ^ LDINTR_STATUS_MASK); + spin_unlock_irqrestore(&sdev->irq_lock, flags); + + if (status & LDINTR_VES) { + drm_handle_vblank(dev, 0); + shmob_drm_crtc_finish_page_flip(&sdev->crtc); + } + + return IRQ_HANDLED; +} + +static int shmob_drm_enable_vblank(struct drm_device *dev, int crtc) +{ + struct shmob_drm_device *sdev = dev->dev_private; + + shmob_drm_crtc_enable_vblank(sdev, true); + + return 0; +} + +static void shmob_drm_disable_vblank(struct drm_device *dev, int crtc) +{ + struct shmob_drm_device *sdev = dev->dev_private; + + shmob_drm_crtc_enable_vblank(sdev, false); +} + +static const struct vm_operations_struct shmob_drm_gem_vm_ops = { + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static const struct file_operations shmob_drm_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .fasync = drm_fasync, + .llseek = no_llseek, + .mmap = shmob_drm_gem_mmap, +}; + +static struct drm_driver shmob_drm_driver = { + .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET, + .load = shmob_drm_load, + .unload = shmob_drm_unload, + .preclose = shmob_drm_preclose, + .irq_handler = shmob_drm_irq, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = shmob_drm_enable_vblank, + .disable_vblank = shmob_drm_disable_vblank, + .gem_free_object = shmob_drm_gem_free_object, + .gem_vm_ops = &shmob_drm_gem_vm_ops, + .dumb_create = shmob_drm_gem_dumb_create, + .dumb_map_offset = shmob_drm_gem_dumb_map_offset, + .dumb_destroy = shmob_drm_gem_dumb_destroy, + .fops = &shmob_drm_fops, + .name = "shmob-drm", + .desc = "Renesas SH Mobile DRM", + .date = "20120424", + .major = 1, + .minor = 0, +}; + +/* ----------------------------------------------------------------------------- + * Power management + */ + +#if CONFIG_PM_SLEEP +static int shmob_drm_pm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *ddev = platform_get_drvdata(pdev); + struct shmob_drm_device *sdev = ddev->dev_private; + + drm_kms_helper_poll_disable(ddev); + shmob_drm_crtc_suspend(&sdev->crtc); + + return 0; +} + +static int shmob_drm_pm_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *ddev = platform_get_drvdata(pdev); + struct shmob_drm_device *sdev = ddev->dev_private; + + mutex_lock(&sdev->ddev->mode_config.mutex); + shmob_drm_crtc_resume(&sdev->crtc); + mutex_unlock(&sdev->ddev->mode_config.mutex); + + drm_kms_helper_poll_enable(sdev->ddev); + return 0; +} +#endif + +static const struct dev_pm_ops shmob_drm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(shmob_drm_pm_suspend, shmob_drm_pm_resume) +}; + +/* ----------------------------------------------------------------------------- + * Platform driver + */ + +static int __devinit shmob_drm_probe(struct platform_device *pdev) +{ + return drm_platform_init(&shmob_drm_driver, pdev); +} + +static int __devexit shmob_drm_remove(struct platform_device *pdev) +{ + drm_platform_exit(&shmob_drm_driver, pdev); + + return 0; +} + +static struct platform_driver shmob_drm_platform_driver = { + .probe = shmob_drm_probe, + .remove = __devexit_p(shmob_drm_remove), + .driver = { + .owner = THIS_MODULE, + .name = "shmob-drm", + .pm = &shmob_drm_pm_ops, + }, +}; + +module_platform_driver(shmob_drm_platform_driver); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("Renesas SH Mobile DRM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/shmobile/shmob_drm_drv.h b/drivers/gpu/drm/shmobile/shmob_drm_drv.h new file mode 100644 index 0000000..5bbaff9 --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_drv.h @@ -0,0 +1,52 @@ +/* + * shmob_drm.h -- SH Mobile DRM driver + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SHMOB_DRM_DRV_H__ +#define __SHMOB_DRM_DRV_H__ + +#include <linux/kernel.h> +#include <linux/spinlock.h> + +#include <drm/shmob_drm.h> + +#include "shmob_drm_crtc.h" + +struct clk; +struct device; +struct drm_device; +struct sh_mobile_meram_info; + +struct shmob_drm_device { + struct device *dev; + const struct shmob_drm_platform_data *pdata; + + void __iomem *mmio; + struct clk *clock; + struct sh_mobile_meram_info *meram; + u32 lddckr; + u32 ldmt1r; + + spinlock_t irq_lock; /* Protects hardware LDINTR register */ + + struct drm_device *ddev; + + struct shmob_drm_crtc crtc; + struct shmob_drm_encoder encoder; + struct shmob_drm_connector connector; + + void *dma_mem; + unsigned long dma_size; + dma_addr_t dma_handle; +}; + +#endif /* __SHMOB_DRM_DRV_H__ */ diff --git a/drivers/gpu/drm/shmobile/shmob_drm_gem.c b/drivers/gpu/drm/shmobile/shmob_drm_gem.c new file mode 100644 index 0000000..8c9aba1 --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_gem.c @@ -0,0 +1,195 @@ +/* + * shmob_drm_gem.c -- SH Mobile DRM GEM + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/slab.h> + +#include <drm/drmP.h> + +#include "shmob_drm_gem.h" + +void shmob_drm_gem_free_object(struct drm_gem_object *obj) +{ + struct shmob_drm_gem_object *sobj = to_shmob_gem_object(obj); + + if (sobj->vaddr) + dma_free_coherent(obj->dev->dev, obj->size, sobj->vaddr, + sobj->dma); + if (obj->map_list.map) + drm_gem_free_mmap_offset(obj); + + drm_gem_object_release(obj); + kfree(sobj); +} + +int shmob_drm_gem_dumb_create(struct drm_file *file_priv, + struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct shmob_drm_gem_object *sobj; + struct drm_gem_object *obj; + int ret; + + sobj = kzalloc(sizeof(*sobj), GFP_KERNEL); + if (sobj == NULL) + return -ENOMEM; + + obj = &sobj->base; + + args->pitch = args->width * args->bpp >> 3; + args->size = PAGE_ALIGN(args->pitch * args->height); + + ret = drm_gem_private_object_init(dev, obj, args->size); + if (ret < 0) { + kfree(sobj); + return ret; + } + + /* Allocated memory for the object. As the hardware only supports DMA + * coherent memory we can do this up-front. + */ + sobj->vaddr = dma_alloc_coherent(dev->dev, args->size, &sobj->dma, + GFP_KERNEL); + if (sobj->vaddr == NULL) { + shmob_drm_gem_free_object(obj); + return -ENOMEM; + } + + printk(KERN_INFO "%s: BO at 0x%08x: %llu (%u) bytes\n", __func__, + sobj->dma, args->size, obj->size); + /* Create a handle. The handle will take a reference to the object so we + * can safely drop the initial reference. + */ + ret = drm_gem_handle_create(file_priv, obj, &args->handle); + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +int shmob_drm_gem_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, + uint64_t *offset) +{ + struct drm_gem_object *obj; + int ret = 0; + + DRM_DEBUG_KMS("\n"); + + mutex_lock(&dev->struct_mutex); + + obj = drm_gem_object_lookup(dev, file_priv, handle); + if (!obj) { + dev_err(dev->dev, "failed to lookup GEM object.\n"); + ret = -EINVAL; + goto unlock; + } + + if (!obj->map_list.map) { + ret = drm_gem_create_mmap_offset(obj); + if (ret) + goto out; + } + + *offset = (u64)obj->map_list.hash.key << PAGE_SHIFT; + DRM_DEBUG_KMS("offset = 0x%lx\n", (unsigned long)*offset); + +out: + drm_gem_object_unreference(obj); +unlock: + mutex_unlock(&dev->struct_mutex); + return ret; +} + +int shmob_drm_gem_dumb_destroy(struct drm_file *file_priv, + struct drm_device *dev, unsigned int handle) +{ + int ret; + + DRM_DEBUG_KMS("\n"); + + ret = drm_gem_handle_delete(file_priv, handle); + if (ret < 0) + dev_err(dev->dev, "failed to delete drm_gem_handle.\n"); + + return ret; +} + +int shmob_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct drm_file *priv = filp->private_data; + struct drm_device *dev = priv->minor->dev; + struct drm_gem_mm *mm = dev->mm_private; + struct shmob_drm_gem_object *sobj; + struct drm_local_map *map; + struct drm_gem_object *obj; + struct drm_hash_item *hash; + pgprot_t prot; + int ret; + + if (drm_device_is_unplugged(dev)) + return -ENODEV; + + mutex_lock(&dev->struct_mutex); + + if (drm_ht_find_item(&mm->offset_hash, vma->vm_pgoff, &hash)) { + ret = -EINVAL; + goto out_unlock; + } + + map = drm_hash_entry(hash, struct drm_map_list, hash)->map; + if (!map || + ((map->flags & _DRM_RESTRICTED) && !capable(CAP_SYS_ADMIN))) { + ret = -EPERM; + goto out_unlock; + } + + /* Check for valid size. */ + if (map->size < vma->vm_end - vma->vm_start) { + ret = -EINVAL; + goto out_unlock; + } + + obj = map->handle; + if (!obj->dev->driver->gem_vm_ops) { + ret = -EINVAL; + goto out_unlock; + } + + sobj = to_shmob_gem_object(obj); + prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + ret = remap_pfn_range(vma, vma->vm_start, sobj->dma >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, prot); + if (ret < 0) + goto out_unlock; + + vma->vm_flags |= VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND; + vma->vm_ops = obj->dev->driver->gem_vm_ops; + vma->vm_private_data = obj; + vma->vm_page_prot = prot; + + /* Take a ref for this mapping of the object, so that the fault + * handler can dereference the mmap offset's pointer to the object. + * This reference is cleaned up by the corresponding vm_close + * (which should happen whether the vma was created by this call, or + * by a vm_open due to mremap or partial unmap or whatever). + */ + drm_gem_object_reference(obj); + + drm_vm_open_locked(dev, vma); + +out_unlock: + mutex_unlock(&dev->struct_mutex); + + return ret; +} diff --git a/drivers/gpu/drm/shmobile/shmob_drm_gem.h b/drivers/gpu/drm/shmobile/shmob_drm_gem.h new file mode 100644 index 0000000..f1c6cb0 --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_gem.h @@ -0,0 +1,46 @@ +/* + * shmob_drm_gem.h -- SH Mobile DRM GEM + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SHMOB_DRM_GEM_H__ +#define __SHMOB_DRM_GEM_H__ + +#include <linux/dma-mapping.h> + +#include <drm/drmP.h> + +struct vm_area_struct; +struct vm_fault; + +struct shmob_drm_gem_object { + struct drm_gem_object base; + + void *vaddr; + dma_addr_t dma; +}; + +#define to_shmob_gem_object(obj) \ + container_of(obj, struct shmob_drm_gem_object, base) + +void shmob_drm_gem_free_object(struct drm_gem_object *obj); +int shmob_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); + +int shmob_drm_gem_dumb_create(struct drm_file *file_priv, + struct drm_device *dev, + struct drm_mode_create_dumb *args); +int shmob_drm_gem_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, + uint64_t *offset); +int shmob_drm_gem_dumb_destroy(struct drm_file *file_priv, + struct drm_device *dev, unsigned int handle); + +#endif /* __SHMOB_DRM_GEM_H__ */ diff --git a/drivers/gpu/drm/shmobile/shmob_drm_kms.c b/drivers/gpu/drm/shmobile/shmob_drm_kms.c new file mode 100644 index 0000000..c3b2d09 --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_kms.c @@ -0,0 +1,211 @@ +/* + * shmob_drm_kms.c -- SH Mobile DRM Mode Setting + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> + +#include <video/sh_mobile_meram.h> + +#include "shmob_drm_crtc.h" +#include "shmob_drm_drv.h" +#include "shmob_drm_gem.h" +#include "shmob_drm_kms.h" +#include "shmob_drm_regs.h" + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct shmob_drm_format_info shmob_drm_format_infos[] = { + { + .fourcc = DRM_FORMAT_RGB565, + .bpp = 16, + .yuv = false, + .lddfr = LDDFR_PKF_RGB16, + .meram = SH_MOBILE_MERAM_PF_RGB, + }, { + .fourcc = DRM_FORMAT_RGB888, + .bpp = 24, + .yuv = false, + .lddfr = LDDFR_PKF_RGB24, + .meram = SH_MOBILE_MERAM_PF_RGB, + }, { + .fourcc = DRM_FORMAT_ARGB8888, + .bpp = 32, + .yuv = false, + .lddfr = LDDFR_PKF_ARGB32, + .meram = SH_MOBILE_MERAM_PF_RGB, + }, { + .fourcc = DRM_FORMAT_NV12, + .bpp = 12, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_420, + .meram = SH_MOBILE_MERAM_PF_NV, + }, { + .fourcc = DRM_FORMAT_NV21, + .bpp = 12, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_420, + .meram = SH_MOBILE_MERAM_PF_NV, + }, { + .fourcc = DRM_FORMAT_NV16, + .bpp = 16, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_422, + .meram = SH_MOBILE_MERAM_PF_NV, + }, { + .fourcc = DRM_FORMAT_NV61, + .bpp = 16, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_422, + .meram = SH_MOBILE_MERAM_PF_NV, + }, { + .fourcc = DRM_FORMAT_NV24, + .bpp = 24, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_444, + .meram = SH_MOBILE_MERAM_PF_NV24, + }, { + .fourcc = DRM_FORMAT_NV42, + .bpp = 24, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_444, + .meram = SH_MOBILE_MERAM_PF_NV24, + }, +}; + +const struct shmob_drm_format_info *shmob_drm_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(shmob_drm_format_infos); ++i) { + if (shmob_drm_format_infos[i].fourcc == fourcc) + return &shmob_drm_format_infos[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Frame buffer + */ + +static int shmob_drm_framebuffer_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct shmob_drm_framebuffer *sfb = to_shmob_framebuffer(fb); + + return drm_gem_handle_create(file_priv, &sfb->sobj[0]->base, handle); +} + +static void shmob_drm_framebuffer_destroy(struct drm_framebuffer *fb) +{ + struct shmob_drm_framebuffer *sfb = to_shmob_framebuffer(fb); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sfb->sobj); ++i) { + if (sfb->sobj[i] != NULL) { + struct drm_gem_object *obj = &sfb->sobj[i]->base; + drm_gem_object_unreference_unlocked(obj); + } + } + + drm_framebuffer_cleanup(fb); + kfree(sfb); +} + +static const struct drm_framebuffer_funcs shmob_drm_framebuffer_funcs = { + .create_handle = shmob_drm_framebuffer_create_handle, + .destroy = shmob_drm_framebuffer_destroy, +}; + +static struct drm_framebuffer * +shmob_drm_fb_create(struct drm_device *dev, struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + const struct shmob_drm_format_info *format; + struct shmob_drm_framebuffer *sfb; + struct drm_framebuffer *fb; + struct drm_gem_object *obj; + unsigned int i; + int ret; + + format = shmob_drm_format_info(mode_cmd->pixel_format); + if (format == NULL) { + dev_dbg(dev->dev, "unsupported pixel format %08x\n", + mode_cmd->pixel_format); + return ERR_PTR(-EINVAL); + } + + sfb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (sfb == NULL) + return ERR_PTR(-ENOMEM); + + sfb->format = format; + fb = &sfb->base; + + for (i = 0; i < (format->yuv ? 2 : 1); ++i) { + obj = drm_gem_object_lookup(dev, file_priv, + mode_cmd->handles[i]); + if (obj == NULL) { + dev_dbg(dev->dev, "GEM object %u not found\n", + mode_cmd->handles[i]); + ret = -ENOENT; + goto error; + } + sfb->sobj[i] = to_shmob_gem_object(obj); + } + + ret = drm_framebuffer_init(dev, fb, &shmob_drm_framebuffer_funcs); + if (ret < 0) + goto error; + + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + return fb; + +error: + for (i = 0; i < ARRAY_SIZE(sfb->sobj); ++i) { + if (sfb->sobj[i] != NULL) { + struct drm_gem_object *obj = &sfb->sobj[i]->base; + drm_gem_object_unreference_unlocked(obj); + } + } + + kfree(sfb); + return ERR_PTR(ret); +} + +static const struct drm_mode_config_funcs shmob_drm_mode_config_funcs = { + .fb_create = shmob_drm_fb_create, +}; + +int shmob_drm_modeset_init(struct shmob_drm_device *sdev) +{ + drm_mode_config_init(sdev->ddev); + + shmob_drm_crtc_create(sdev); + shmob_drm_encoder_create(sdev); + shmob_drm_connector_create(sdev, &sdev->encoder.encoder); + + drm_kms_helper_poll_init(sdev->ddev); + + sdev->ddev->mode_config.min_width = 0; + sdev->ddev->mode_config.min_height = 0; + sdev->ddev->mode_config.max_width = 4095; + sdev->ddev->mode_config.max_height = 4095; + sdev->ddev->mode_config.funcs = &shmob_drm_mode_config_funcs; + + return 0; +} diff --git a/drivers/gpu/drm/shmobile/shmob_drm_kms.h b/drivers/gpu/drm/shmobile/shmob_drm_kms.h new file mode 100644 index 0000000..da2da38 --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_kms.h @@ -0,0 +1,44 @@ +/* + * shmob_drm_kms.h -- SH Mobile DRM Mode Setting + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SHMOB_DRM_KMS_H__ +#define __SHMOB_DRM_KMS_H__ + +#include <linux/types.h> + +#include "shmob_drm_gem.h" + +struct shmob_drm_device; + +struct shmob_drm_format_info { + u32 fourcc; + unsigned int bpp; + bool yuv; + u32 lddfr; + unsigned int meram; +}; + +struct shmob_drm_framebuffer { + struct drm_framebuffer base; + struct shmob_drm_gem_object *sobj[2]; + const struct shmob_drm_format_info *format; +}; + +#define to_shmob_framebuffer(fb) \ + container_of(fb, struct shmob_drm_framebuffer, base) + +const struct shmob_drm_format_info *shmob_drm_format_info(u32 fourcc); + +int shmob_drm_modeset_init(struct shmob_drm_device *sdev); + +#endif /* __SHMOB_DRM_KMS_H__ */ diff --git a/drivers/gpu/drm/shmobile/shmob_drm_plane.c b/drivers/gpu/drm/shmobile/shmob_drm_plane.c new file mode 100644 index 0000000..9794429 --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_plane.c @@ -0,0 +1,240 @@ +/* + * shmob_drm_crtc.c -- SH Mobile DRM CRTCs + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> + +#include <video/sh_mobile_meram.h> + +#include "shmob_drm_drv.h" +#include "shmob_drm_kms.h" +#include "shmob_drm_plane.h" +#include "shmob_drm_regs.h" + +struct shmob_drm_plane { + struct drm_plane plane; + unsigned int index; + unsigned int alpha; + + unsigned long dma[2]; + + unsigned int src_x; + unsigned int src_y; + unsigned int crtc_x; + unsigned int crtc_y; + unsigned int crtc_w; + unsigned int crtc_h; +}; + +#define to_shmob_plane(p) container_of(p, struct shmob_drm_plane, plane) + +static void shmob_drm_plane_compute_base(struct shmob_drm_plane *splane, + struct shmob_drm_framebuffer *sfb, + int x, int y) +{ + unsigned int bpp; + + bpp = sfb->format->yuv ? 8 : sfb->format->bpp; + splane->dma[0] = sfb->sobj[0]->dma + y * sfb->base.pitches[0] + + x * bpp / 8; + + if (sfb->format->yuv) { + bpp = sfb->format->bpp - 8; + splane->dma[1] = sfb->sobj[1]->dma + + y * sfb->base.pitches[0] * bpp / 8 + + x * (bpp == 16 ? 2 : 1); + } +} + +static void __shmob_drm_plane_setup(struct shmob_drm_plane *splane, + struct shmob_drm_framebuffer *sfb) +{ + struct shmob_drm_device *sdev = splane->plane.dev->dev_private; + u32 format; + + /* TODO: Support ROP3 mode */ + format = LDBBSIFR_EN | (splane->alpha << LDBBSIFR_LAY_SHIFT); + + switch (sfb->format->fourcc) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV42: + format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW; + break; + case DRM_FORMAT_RGB888: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV24: + format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW | LDBBSIFR_SWPB; + break; + case DRM_FORMAT_ARGB8888: + default: + format |= LDBBSIFR_SWPL; + break; + } + + switch (sfb->format->fourcc) { + case DRM_FORMAT_RGB565: + format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB16; + break; + case DRM_FORMAT_RGB888: + format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB24; + break; + case DRM_FORMAT_ARGB8888: + format |= LDBBSIFR_AL_PK | LDBBSIFR_RY | LDDFR_PKF_ARGB32; + break; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_420; + break; + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_422; + break; + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV42: + format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_444; + break; + } + +#define plane_reg_dump(sdev, splane, reg) \ + dev_dbg(sdev->ddev->dev, "%s(%u): %s 0x%08x 0x%08x\n", __func__, \ + splane->index, #reg, \ + lcdc_read(sdev, reg(splane->index)), \ + lcdc_read(sdev, reg(splane->index) + LCDC_SIDE_B_OFFSET)) + + plane_reg_dump(sdev, splane, LDBnBSIFR); + plane_reg_dump(sdev, splane, LDBnBSSZR); + plane_reg_dump(sdev, splane, LDBnBLOCR); + plane_reg_dump(sdev, splane, LDBnBSMWR); + plane_reg_dump(sdev, splane, LDBnBSAYR); + plane_reg_dump(sdev, splane, LDBnBSACR); + + lcdc_write(sdev, LDBCR, LDBCR_UPC(splane->index)); + dev_dbg(sdev->ddev->dev, "%s(%u): %s 0x%08x\n", __func__, splane->index, + "LDBCR", lcdc_read(sdev, LDBCR)); + + lcdc_write(sdev, LDBnBSIFR(splane->index), format); + + lcdc_write(sdev, LDBnBSSZR(splane->index), + (splane->crtc_h << LDBBSSZR_BVSS_SHIFT) | + (splane->crtc_w << LDBBSSZR_BHSS_SHIFT)); + lcdc_write(sdev, LDBnBLOCR(splane->index), + (splane->crtc_y << LDBBLOCR_CVLC_SHIFT) | + (splane->crtc_x << LDBBLOCR_CHLC_SHIFT)); + lcdc_write(sdev, LDBnBSMWR(splane->index), + sfb->base.pitches[0] << LDBBSMWR_BSMW_SHIFT); + + shmob_drm_plane_compute_base(splane, sfb, splane->src_x, splane->src_y); + + lcdc_write(sdev, LDBnBSAYR(splane->index), splane->dma[0]); + if (sfb->format->yuv) + lcdc_write(sdev, LDBnBSACR(splane->index), splane->dma[1]); + + lcdc_write(sdev, LDBCR, + LDBCR_UPF(splane->index) | LDBCR_UPD(splane->index)); + dev_dbg(sdev->ddev->dev, "%s(%u): %s 0x%08x\n", __func__, splane->index, + "LDBCR", lcdc_read(sdev, LDBCR)); + + plane_reg_dump(sdev, splane, LDBnBSIFR); + plane_reg_dump(sdev, splane, LDBnBSSZR); + plane_reg_dump(sdev, splane, LDBnBLOCR); + plane_reg_dump(sdev, splane, LDBnBSMWR); + plane_reg_dump(sdev, splane, LDBnBSAYR); + plane_reg_dump(sdev, splane, LDBnBSACR); +} + +void shmob_drm_plane_setup(struct drm_plane *plane) +{ + struct shmob_drm_plane *splane = to_shmob_plane(plane); + + if (plane->fb == NULL || !plane->enabled) + return; + + __shmob_drm_plane_setup(splane, to_shmob_framebuffer(plane->fb)); +} + +static int +shmob_drm_plane_update(struct drm_plane *plane, 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) +{ + struct shmob_drm_plane *splane = to_shmob_plane(plane); + struct shmob_drm_framebuffer *sfb = to_shmob_framebuffer(fb); + + splane->src_x = src_x >> 16; + splane->src_y = src_y >> 16; + splane->crtc_x = crtc_x; + splane->crtc_y = crtc_y; + splane->crtc_w = crtc_w; + splane->crtc_h = crtc_h; + + __shmob_drm_plane_setup(splane, sfb); + return 0; +} + +static int shmob_drm_plane_disable(struct drm_plane *plane) +{ + struct shmob_drm_plane *splane = to_shmob_plane(plane); + struct shmob_drm_device *sdev = plane->dev->dev_private; + + lcdc_write(sdev, LDBnBSIFR(splane->index), 0); + return 0; +} + +static void shmob_drm_plane_destroy(struct drm_plane *plane) +{ + struct shmob_drm_plane *splane = to_shmob_plane(plane); + + shmob_drm_plane_disable(plane); + drm_plane_cleanup(plane); + kfree(splane); +} + +static const struct drm_plane_funcs shmob_drm_plane_funcs = { + .update_plane = shmob_drm_plane_update, + .disable_plane = shmob_drm_plane_disable, + .destroy = shmob_drm_plane_destroy, +}; + +static const uint32_t formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, + DRM_FORMAT_NV61, + DRM_FORMAT_NV24, + DRM_FORMAT_NV42, +}; + +int shmob_drm_plane_create(struct shmob_drm_device *sdev, unsigned int index) +{ + struct shmob_drm_plane *splane; + + splane = kzalloc(sizeof(*splane), GFP_KERNEL); + if (splane == NULL) + return -ENOMEM; + + splane->index = index; + splane->alpha = 255; + + return drm_plane_init(sdev->ddev, &splane->plane, 1, + &shmob_drm_plane_funcs, formats, + ARRAY_SIZE(formats), false); +} diff --git a/drivers/gpu/drm/shmobile/shmob_drm_plane.h b/drivers/gpu/drm/shmobile/shmob_drm_plane.h new file mode 100644 index 0000000..99623d0 --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_plane.h @@ -0,0 +1,22 @@ +/* + * shmob_drm_plane.h -- SH Mobile DRM Planes + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SHMOB_DRM_PLANE_H__ +#define __SHMOB_DRM_PLANE_H__ + +struct shmob_drm_device; + +int shmob_drm_plane_create(struct shmob_drm_device *sdev, unsigned int index); +void shmob_drm_plane_setup(struct drm_plane *plane); + +#endif /* __SHMOB_DRM_PLANE_H__ */ diff --git a/drivers/gpu/drm/shmobile/shmob_drm_regs.h b/drivers/gpu/drm/shmobile/shmob_drm_regs.h new file mode 100644 index 0000000..a90517e --- /dev/null +++ b/drivers/gpu/drm/shmobile/shmob_drm_regs.h @@ -0,0 +1,304 @@ +/* + * shmob_drm_regs.g -- SH Mobile DRM registers + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SHMOB_DRM_REGS_H__ +#define __SHMOB_DRM_REGS_H__ + +#include <linux/io.h> + +/* Register definitions */ +#define LDDCKPAT1R 0x400 +#define LDDCKPAT2R 0x404 +#define LDDCKR 0x410 +#define LDDCKR_ICKSEL_BUS (0 << 16) +#define LDDCKR_ICKSEL_MIPI (1 << 16) +#define LDDCKR_ICKSEL_HDMI (2 << 16) +#define LDDCKR_ICKSEL_EXT (3 << 16) +#define LDDCKR_ICKSEL_MASK (7 << 16) +#define LDDCKR_MOSEL (1 << 6) +#define LDDCKSTPR 0x414 +#define LDDCKSTPR_DCKSTS (1 << 16) +#define LDDCKSTPR_DCKSTP (1 << 0) +#define LDMT1R 0x418 +#define LDMT1R_VPOL (1 << 28) +#define LDMT1R_HPOL (1 << 27) +#define LDMT1R_DWPOL (1 << 26) +#define LDMT1R_DIPOL (1 << 25) +#define LDMT1R_DAPOL (1 << 24) +#define LDMT1R_HSCNT (1 << 17) +#define LDMT1R_DWCNT (1 << 16) +#define LDMT1R_IFM (1 << 12) +#define LDMT1R_MIFTYP_RGB8 (0x0 << 0) +#define LDMT1R_MIFTYP_RGB9 (0x4 << 0) +#define LDMT1R_MIFTYP_RGB12A (0x5 << 0) +#define LDMT1R_MIFTYP_RGB12B (0x6 << 0) +#define LDMT1R_MIFTYP_RGB16 (0x7 << 0) +#define LDMT1R_MIFTYP_RGB18 (0xa << 0) +#define LDMT1R_MIFTYP_RGB24 (0xb << 0) +#define LDMT1R_MIFTYP_YCBCR (0xf << 0) +#define LDMT1R_MIFTYP_SYS8A (0x0 << 0) +#define LDMT1R_MIFTYP_SYS8B (0x1 << 0) +#define LDMT1R_MIFTYP_SYS8C (0x2 << 0) +#define LDMT1R_MIFTYP_SYS8D (0x3 << 0) +#define LDMT1R_MIFTYP_SYS9 (0x4 << 0) +#define LDMT1R_MIFTYP_SYS12 (0x5 << 0) +#define LDMT1R_MIFTYP_SYS16A (0x7 << 0) +#define LDMT1R_MIFTYP_SYS16B (0x8 << 0) +#define LDMT1R_MIFTYP_SYS16C (0x9 << 0) +#define LDMT1R_MIFTYP_SYS18 (0xa << 0) +#define LDMT1R_MIFTYP_SYS24 (0xb << 0) +#define LDMT1R_MIFTYP_MASK (0xf << 0) +#define LDMT2R 0x41c +#define LDMT2R_CSUP_MASK (7 << 26) +#define LDMT2R_CSUP_SHIFT 26 +#define LDMT2R_RSV (1 << 25) +#define LDMT2R_VSEL (1 << 24) +#define LDMT2R_WCSC_MASK (0xff << 16) +#define LDMT2R_WCSC_SHIFT 16 +#define LDMT2R_WCEC_MASK (0xff << 8) +#define LDMT2R_WCEC_SHIFT 8 +#define LDMT2R_WCLW_MASK (0xff << 0) +#define LDMT2R_WCLW_SHIFT 0 +#define LDMT3R 0x420 +#define LDMT3R_RDLC_MASK (0x3f << 24) +#define LDMT3R_RDLC_SHIFT 24 +#define LDMT3R_RCSC_MASK (0xff << 16) +#define LDMT3R_RCSC_SHIFT 16 +#define LDMT3R_RCEC_MASK (0xff << 8) +#define LDMT3R_RCEC_SHIFT 8 +#define LDMT3R_RCLW_MASK (0xff << 0) +#define LDMT3R_RCLW_SHIFT 0 +#define LDDFR 0x424 +#define LDDFR_CF1 (1 << 18) +#define LDDFR_CF0 (1 << 17) +#define LDDFR_CC (1 << 16) +#define LDDFR_YF_420 (0 << 8) +#define LDDFR_YF_422 (1 << 8) +#define LDDFR_YF_444 (2 << 8) +#define LDDFR_YF_MASK (3 << 8) +#define LDDFR_PKF_ARGB32 (0x00 << 0) +#define LDDFR_PKF_RGB16 (0x03 << 0) +#define LDDFR_PKF_RGB24 (0x0b << 0) +#define LDDFR_PKF_MASK (0x1f << 0) +#define LDSM1R 0x428 +#define LDSM1R_OS (1 << 0) +#define LDSM2R 0x42c +#define LDSM2R_OSTRG (1 << 0) +#define LDSA1R 0x430 +#define LDSA2R 0x434 +#define LDMLSR 0x438 +#define LDWBFR 0x43c +#define LDWBCNTR 0x440 +#define LDWBAR 0x444 +#define LDHCNR 0x448 +#define LDHSYNR 0x44c +#define LDVLNR 0x450 +#define LDVSYNR 0x454 +#define LDHPDR 0x458 +#define LDVPDR 0x45c +#define LDPMR 0x460 +#define LDPMR_LPS (3 << 0) +#define LDINTR 0x468 +#define LDINTR_FE (1 << 10) +#define LDINTR_VSE (1 << 9) +#define LDINTR_VEE (1 << 8) +#define LDINTR_FS (1 << 2) +#define LDINTR_VSS (1 << 1) +#define LDINTR_VES (1 << 0) +#define LDINTR_STATUS_MASK (0xff << 0) +#define LDSR 0x46c +#define LDSR_MSS (1 << 10) +#define LDSR_MRS (1 << 8) +#define LDSR_AS (1 << 1) +#define LDCNT1R 0x470 +#define LDCNT1R_DE (1 << 0) +#define LDCNT2R 0x474 +#define LDCNT2R_BR (1 << 8) +#define LDCNT2R_MD (1 << 3) +#define LDCNT2R_SE (1 << 2) +#define LDCNT2R_ME (1 << 1) +#define LDCNT2R_DO (1 << 0) +#define LDRCNTR 0x478 +#define LDRCNTR_SRS (1 << 17) +#define LDRCNTR_SRC (1 << 16) +#define LDRCNTR_MRS (1 << 1) +#define LDRCNTR_MRC (1 << 0) +#define LDDDSR 0x47c +#define LDDDSR_LS (1 << 2) +#define LDDDSR_WS (1 << 1) +#define LDDDSR_BS (1 << 0) +#define LDHAJR 0x4a0 + +#define LDDWD0R 0x800 +#define LDDWDxR_WDACT (1 << 28) +#define LDDWDxR_RSW (1 << 24) +#define LDDRDR 0x840 +#define LDDRDR_RSR (1 << 24) +#define LDDRDR_DRD_MASK (0x3ffff << 0) +#define LDDWAR 0x900 +#define LDDWAR_WA (1 << 0) +#define LDDRAR 0x904 +#define LDDRAR_RA (1 << 0) + +#define LDBCR 0xb00 +#define LDBCR_UPC(n) (1 << ((n) + 16)) +#define LDBCR_UPF(n) (1 << ((n) + 8)) +#define LDBCR_UPD(n) (1 << ((n) + 0)) +#define LDBnBSIFR(n) (0xb20 + (n) * 0x20 + 0x00) +#define LDBBSIFR_EN (1 << 31) +#define LDBBSIFR_VS (1 << 29) +#define LDBBSIFR_BRSEL (1 << 28) +#define LDBBSIFR_MX (1 << 27) +#define LDBBSIFR_MY (1 << 26) +#define LDBBSIFR_CV3 (3 << 24) +#define LDBBSIFR_CV2 (2 << 24) +#define LDBBSIFR_CV1 (1 << 24) +#define LDBBSIFR_CV0 (0 << 24) +#define LDBBSIFR_CV_MASK (3 << 24) +#define LDBBSIFR_LAY_MASK (0xff << 16) +#define LDBBSIFR_LAY_SHIFT 16 +#define LDBBSIFR_ROP3_MASK (0xff << 16) +#define LDBBSIFR_ROP3_SHIFT 16 +#define LDBBSIFR_AL_PL8 (3 << 14) +#define LDBBSIFR_AL_PL1 (2 << 14) +#define LDBBSIFR_AL_PK (1 << 14) +#define LDBBSIFR_AL_1 (0 << 14) +#define LDBBSIFR_AL_MASK (3 << 14) +#define LDBBSIFR_SWPL (1 << 10) +#define LDBBSIFR_SWPW (1 << 9) +#define LDBBSIFR_SWPB (1 << 8) +#define LDBBSIFR_RY (1 << 7) +#define LDBBSIFR_CHRR_420 (2 << 0) +#define LDBBSIFR_CHRR_422 (1 << 0) +#define LDBBSIFR_CHRR_444 (0 << 0) +#define LDBBSIFR_RPKF_ARGB32 (0x00 << 0) +#define LDBBSIFR_RPKF_RGB16 (0x03 << 0) +#define LDBBSIFR_RPKF_RGB24 (0x0b << 0) +#define LDBBSIFR_RPKF_MASK (0x1f << 0) +#define LDBnBSSZR(n) (0xb20 + (n) * 0x20 + 0x04) +#define LDBBSSZR_BVSS_MASK (0xfff << 16) +#define LDBBSSZR_BVSS_SHIFT 16 +#define LDBBSSZR_BHSS_MASK (0xfff << 0) +#define LDBBSSZR_BHSS_SHIFT 0 +#define LDBnBLOCR(n) (0xb20 + (n) * 0x20 + 0x08) +#define LDBBLOCR_CVLC_MASK (0xfff << 16) +#define LDBBLOCR_CVLC_SHIFT 16 +#define LDBBLOCR_CHLC_MASK (0xfff << 0) +#define LDBBLOCR_CHLC_SHIFT 0 +#define LDBnBSMWR(n) (0xb20 + (n) * 0x20 + 0x0c) +#define LDBBSMWR_BSMWA_MASK (0xffff << 16) +#define LDBBSMWR_BSMWA_SHIFT 16 +#define LDBBSMWR_BSMW_MASK (0xffff << 0) +#define LDBBSMWR_BSMW_SHIFT 0 +#define LDBnBSAYR(n) (0xb20 + (n) * 0x20 + 0x10) +#define LDBBSAYR_FG1A_MASK (0xff << 24) +#define LDBBSAYR_FG1A_SHIFT 24 +#define LDBBSAYR_FG1R_MASK (0xff << 16) +#define LDBBSAYR_FG1R_SHIFT 16 +#define LDBBSAYR_FG1G_MASK (0xff << 8) +#define LDBBSAYR_FG1G_SHIFT 8 +#define LDBBSAYR_FG1B_MASK (0xff << 0) +#define LDBBSAYR_FG1B_SHIFT 0 +#define LDBnBSACR(n) (0xb20 + (n) * 0x20 + 0x14) +#define LDBBSACR_FG2A_MASK (0xff << 24) +#define LDBBSACR_FG2A_SHIFT 24 +#define LDBBSACR_FG2R_MASK (0xff << 16) +#define LDBBSACR_FG2R_SHIFT 16 +#define LDBBSACR_FG2G_MASK (0xff << 8) +#define LDBBSACR_FG2G_SHIFT 8 +#define LDBBSACR_FG2B_MASK (0xff << 0) +#define LDBBSACR_FG2B_SHIFT 0 +#define LDBnBSAAR(n) (0xb20 + (n) * 0x20 + 0x18) +#define LDBBSAAR_AP_MASK (0xff << 24) +#define LDBBSAAR_AP_SHIFT 24 +#define LDBBSAAR_R_MASK (0xff << 16) +#define LDBBSAAR_R_SHIFT 16 +#define LDBBSAAR_GY_MASK (0xff << 8) +#define LDBBSAAR_GY_SHIFT 8 +#define LDBBSAAR_B_MASK (0xff << 0) +#define LDBBSAAR_B_SHIFT 0 +#define LDBnBPPCR(n) (0xb20 + (n) * 0x20 + 0x1c) +#define LDBBPPCR_AP_MASK (0xff << 24) +#define LDBBPPCR_AP_SHIFT 24 +#define LDBBPPCR_R_MASK (0xff << 16) +#define LDBBPPCR_R_SHIFT 16 +#define LDBBPPCR_GY_MASK (0xff << 8) +#define LDBBPPCR_GY_SHIFT 8 +#define LDBBPPCR_B_MASK (0xff << 0) +#define LDBBPPCR_B_SHIFT 0 +#define LDBnBBGCL(n) (0xb10 + (n) * 0x04) +#define LDBBBGCL_BGA_MASK (0xff << 24) +#define LDBBBGCL_BGA_SHIFT 24 +#define LDBBBGCL_BGR_MASK (0xff << 16) +#define LDBBBGCL_BGR_SHIFT 16 +#define LDBBBGCL_BGG_MASK (0xff << 8) +#define LDBBBGCL_BGG_SHIFT 8 +#define LDBBBGCL_BGB_MASK (0xff << 0) +#define LDBBBGCL_BGB_SHIFT 0 + +#define LCDC_SIDE_B_OFFSET 0x1000 +#define LCDC_MIRROR_OFFSET 0x2000 + +static inline bool lcdc_is_banked(u32 reg) +{ + switch (reg) { + case LDMT1R: + case LDMT2R: + case LDMT3R: + case LDDFR: + case LDSM1R: + case LDSA1R: + case LDSA2R: + case LDMLSR: + case LDWBFR: + case LDWBCNTR: + case LDWBAR: + case LDHCNR: + case LDHSYNR: + case LDVLNR: + case LDVSYNR: + case LDHPDR: + case LDVPDR: + case LDHAJR: + return true; + default: + return reg >= LDBnBBGCL(0) && reg <= LDBnBPPCR(3); + } +} + +static inline void lcdc_write_mirror(struct shmob_drm_device *sdev, u32 reg, + u32 data) +{ + iowrite32(data, sdev->mmio + reg + LCDC_MIRROR_OFFSET); +} + +static inline void lcdc_write(struct shmob_drm_device *sdev, u32 reg, u32 data) +{ + iowrite32(data, sdev->mmio + reg); + if (lcdc_is_banked(reg)) + iowrite32(data, sdev->mmio + reg + LCDC_SIDE_B_OFFSET); +} + +static inline u32 lcdc_read(struct shmob_drm_device *sdev, u32 reg) +{ + return ioread32(sdev->mmio + reg); +} + +static inline void lcdc_wait_bit(struct shmob_drm_device *sdev, u32 reg, + u32 mask, u32 until) +{ + while ((lcdc_read(sdev, reg) & mask) != until) + cpu_relax(); +} + +#endif /* __SHMOB_DRM_REGS_H__ */ diff --git a/include/drm/shmob_drm.h b/include/drm/shmob_drm.h new file mode 100644 index 0000000..7c686d3 --- /dev/null +++ b/include/drm/shmob_drm.h @@ -0,0 +1,99 @@ +/* + * shmob_drm.h -- SH Mobile DRM driver + * + * Copyright (C) 2012 Renesas Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SHMOB_DRM_H__ +#define __SHMOB_DRM_H__ + +#include <linux/kernel.h> + +#include <drm/drm_mode.h> + +struct sh_mobile_meram_cfg; +struct sh_mobile_meram_info; + +enum shmob_drm_clk_source { + SHMOB_DRM_CLK_BUS, + SHMOB_DRM_CLK_PERIPHERAL, + SHMOB_DRM_CLK_EXTERNAL, +}; + +enum shmob_drm_interface { + SHMOB_DRM_IFACE_RGB8, /* 24bpp, 8:8:8 */ + SHMOB_DRM_IFACE_RGB9, /* 18bpp, 9:9 */ + SHMOB_DRM_IFACE_RGB12A, /* 24bpp, 12:12 */ + SHMOB_DRM_IFACE_RGB12B, /* 12bpp */ + SHMOB_DRM_IFACE_RGB16, /* 16bpp */ + SHMOB_DRM_IFACE_RGB18, /* 18bpp */ + SHMOB_DRM_IFACE_RGB24, /* 24bpp */ + SHMOB_DRM_IFACE_YUV422, /* 16bpp */ + SHMOB_DRM_IFACE_SYS8A, /* 24bpp, 8:8:8 */ + SHMOB_DRM_IFACE_SYS8B, /* 18bpp, 8:8:2 */ + SHMOB_DRM_IFACE_SYS8C, /* 18bpp, 2:8:8 */ + SHMOB_DRM_IFACE_SYS8D, /* 16bpp, 8:8 */ + SHMOB_DRM_IFACE_SYS9, /* 18bpp, 9:9 */ + SHMOB_DRM_IFACE_SYS12, /* 24bpp, 12:12 */ + SHMOB_DRM_IFACE_SYS16A, /* 16bpp */ + SHMOB_DRM_IFACE_SYS16B, /* 18bpp, 16:2 */ + SHMOB_DRM_IFACE_SYS16C, /* 18bpp, 2:16 */ + SHMOB_DRM_IFACE_SYS18, /* 18bpp */ + SHMOB_DRM_IFACE_SYS24, /* 24bpp */ +}; + +struct shmob_drm_backlight_data { + const char *name; + int max_brightness; + int (*get_brightness)(void); + int (*set_brightness)(int brightness); +}; + +struct shmob_drm_panel_data { + unsigned int width_mm; /* Panel width in mm */ + unsigned int height_mm; /* Panel height in mm */ + struct drm_mode_modeinfo mode; +}; + +struct shmob_drm_sys_interface_data { + unsigned int read_latch:6; + unsigned int read_setup:8; + unsigned int read_cycle:8; + unsigned int read_strobe:8; + unsigned int write_setup:8; + unsigned int write_cycle:8; + unsigned int write_strobe:8; + unsigned int cs_setup:3; + unsigned int vsync_active_high:1; + unsigned int vsync_dir_input:1; +}; + +#define SHMOB_DRM_IFACE_FL_DWPOL (1 << 0) /* Rising edge dot clock data latch */ +#define SHMOB_DRM_IFACE_FL_DIPOL (1 << 1) /* Active low display enable */ +#define SHMOB_DRM_IFACE_FL_DAPOL (1 << 2) /* Active low display data */ +#define SHMOB_DRM_IFACE_FL_HSCNT (1 << 3) /* Disable HSYNC during VBLANK */ +#define SHMOB_DRM_IFACE_FL_DWCNT (1 << 4) /* Disable dotclock during blanking */ + +struct shmob_drm_interface_data { + enum shmob_drm_interface interface; + struct shmob_drm_sys_interface_data sys; + unsigned int clk_div; + unsigned int flags; +}; + +struct shmob_drm_platform_data { + enum shmob_drm_clk_source clk_source; + struct shmob_drm_interface_data iface; + struct shmob_drm_panel_data panel; + struct shmob_drm_backlight_data backlight; + const struct sh_mobile_meram_cfg *meram; +}; + +#endif /* __SHMOB_DRM_H__ */
Hi Laurent,
On Wed, May 30, 2012 at 02:32:59PM +0200, Laurent Pinchart wrote:
The SH Mobile LCD controller (LCDC) DRM driver supports the main graphics plane in RGB and YUV formats, as well as the overlay planes (in alpha-blending mode only).
Only flat panel outputs using the parallel interface are supported. Support for SYS panels, HDMI and DSI is currently not implemented.
Have you seen
http://lists.freedesktop.org/archives/dri-devel/2012-May/023536.html
and
http://lists.freedesktop.org/archives/dri-devel/2012-May/023544.html
The first patch is a generic gem helper for dma_alloc_* backed memory which could serve as a drop in replacement for your gem code. The latter is a generic helper for the framebuffer handling on these devices. I saw that your driver supports planes which Lars' patch currently doesn't, but it should be easy to add support for this.
I hope these are useful, if not, could you tell what's missing?
Sascha
On 05/30/2012 03:43 PM, Sascha Hauer wrote:
Hi Laurent,
On Wed, May 30, 2012 at 02:32:59PM +0200, Laurent Pinchart wrote:
The SH Mobile LCD controller (LCDC) DRM driver supports the main graphics plane in RGB and YUV formats, as well as the overlay planes (in alpha-blending mode only).
Only flat panel outputs using the parallel interface are supported. Support for SYS panels, HDMI and DSI is currently not implemented.
Have you seen
http://lists.freedesktop.org/archives/dri-devel/2012-May/023536.html
and
http://lists.freedesktop.org/archives/dri-devel/2012-May/023544.html
The first patch is a generic gem helper for dma_alloc_* backed memory which could serve as a drop in replacement for your gem code. The latter is a generic helper for the framebuffer handling on these devices. I saw that your driver supports planes which Lars' patch currently doesn't, but it should be easy to add support for this.
Yup, just added plane support, will repost the patch in a moment.
- Lars
Hi Sascha,
On Wednesday 30 May 2012 15:43:24 Sascha Hauer wrote:
Hi Laurent,
On Wed, May 30, 2012 at 02:32:59PM +0200, Laurent Pinchart wrote:
The SH Mobile LCD controller (LCDC) DRM driver supports the main graphics plane in RGB and YUV formats, as well as the overlay planes (in alpha-blending mode only).
Only flat panel outputs using the parallel interface are supported. Support for SYS panels, HDMI and DSI is currently not implemented.
Have you seen
http://lists.freedesktop.org/archives/dri-devel/2012-May/023536.html
and
http://lists.freedesktop.org/archives/dri-devel/2012-May/023544.html
No I hadn't. Thanks for the information.
The first patch is a generic gem helper for dma_alloc_* backed memory which could serve as a drop in replacement for your gem code. The latter is a generic helper for the framebuffer handling on these devices. I saw that your driver supports planes which Lars' patch currently doesn't, but it should be easy to add support for this.
I hope these are useful, if not, could you tell what's missing?
I'll try to use those and I'll report back.
On Wed, May 30, 2012 at 02:32:59PM +0200, Laurent Pinchart wrote:
+static struct drm_framebuffer * +shmob_drm_fb_create(struct drm_device *dev, struct drm_file *file_priv,
struct drm_mode_fb_cmd2 *mode_cmd)
+{
- const struct shmob_drm_format_info *format;
- struct shmob_drm_framebuffer *sfb;
- struct drm_framebuffer *fb;
- struct drm_gem_object *obj;
- unsigned int i;
- int ret;
- format = shmob_drm_format_info(mode_cmd->pixel_format);
- if (format == NULL) {
dev_dbg(dev->dev, "unsupported pixel format %08x\n",
mode_cmd->pixel_format);
return ERR_PTR(-EINVAL);
- }
- sfb = kzalloc(sizeof(*fb), GFP_KERNEL);
- if (sfb == NULL)
return ERR_PTR(-ENOMEM);
- sfb->format = format;
- fb = &sfb->base;
- for (i = 0; i < (format->yuv ? 2 : 1); ++i) {
obj = drm_gem_object_lookup(dev, file_priv,
mode_cmd->handles[i]);
if (obj == NULL) {
dev_dbg(dev->dev, "GEM object %u not found\n",
mode_cmd->handles[i]);
ret = -ENOENT;
goto error;
}
sfb->sobj[i] = to_shmob_gem_object(obj);
- }
offsets[] not checked, nor is it handled in the code that calculates the final offsets.
Based on the rest of the code, it seems that the hardware assumes pitches[1] == pitches[0]*chroma_cpp. You should reject other values here. Also it's not clear from the code if there are other stride limitations that would need checking.
Also there are no checks to make sure the fb fits inside the gem object. At one point I tried to cook up a generic function to help with such checks. I probably need to revisit that issue and try to make something that'd work for tiled formats as well.
Hi Ville,
Thank you for the review.
On Wednesday 30 May 2012 17:16:46 Ville Syrjälä wrote:
On Wed, May 30, 2012 at 02:32:59PM +0200, Laurent Pinchart wrote:
+static struct drm_framebuffer * +shmob_drm_fb_create(struct drm_device *dev, struct drm_file *file_priv,
struct drm_mode_fb_cmd2 *mode_cmd)
+{
- const struct shmob_drm_format_info *format;
- struct shmob_drm_framebuffer *sfb;
- struct drm_framebuffer *fb;
- struct drm_gem_object *obj;
- unsigned int i;
- int ret;
- format = shmob_drm_format_info(mode_cmd->pixel_format);
- if (format == NULL) {
dev_dbg(dev->dev, "unsupported pixel format %08x\n",
mode_cmd->pixel_format);
return ERR_PTR(-EINVAL);
- }
- sfb = kzalloc(sizeof(*fb), GFP_KERNEL);
- if (sfb == NULL)
return ERR_PTR(-ENOMEM);
- sfb->format = format;
- fb = &sfb->base;
- for (i = 0; i < (format->yuv ? 2 : 1); ++i) {
obj = drm_gem_object_lookup(dev, file_priv,
mode_cmd->handles[i]);
if (obj == NULL) {
dev_dbg(dev->dev, "GEM object %u not found\n",
mode_cmd->handles[i]);
ret = -ENOENT;
goto error;
}
sfb->sobj[i] = to_shmob_gem_object(obj);
- }
offsets[] not checked, nor is it handled in the code that calculates the final offsets.
Based on the rest of the code, it seems that the hardware assumes pitches[1] == pitches[0]*chroma_cpp. You should reject other values here. Also it's not clear from the code if there are other stride limitations that would need checking.
Also there are no checks to make sure the fb fits inside the gem object. At one point I tried to cook up a generic function to help with such checks. I probably need to revisit that issue and try to make something that'd work for tiled formats as well.
You're right. I now use the generic drm_fb_cma_create() function from Lars- Peter "DRM: Add DRM kms/fb cma helper" patch, so the above code is gone. I'll make sure your comments get addressed in the patch.
On 05/30/2012 02:32 PM, Laurent Pinchart wrote:
[...]
- for (i = 0; i < (format->yuv ? 2 : 1); ++i) {
obj = drm_gem_object_lookup(dev, file_priv,
mode_cmd->handles[i]);
if (obj == NULL) {
dev_dbg(dev->dev, "GEM object %u not found\n",
mode_cmd->handles[i]);
ret = -ENOENT;
goto error;
}
sfb->sobj[i] = to_shmob_gem_object(obj);
- }
I added multi-plane support the cma fb helper functions and it seems to work. But all other DRM drivers seem to assume that multi-plane formats still only have a single buffer, while yours seems to assume that there is a plane for each buffer. The exception is the Exynos driver, but it added a new set of formats which are identical to the other formats, but use one buffer per plane.
So I'm not sure how to implement this correctly in a generic fashion.
- Lars
On 05/30/2012 04:45 PM, Lars-Peter Clausen wrote:
On 05/30/2012 02:32 PM, Laurent Pinchart wrote:
[...]
- for (i = 0; i < (format->yuv ? 2 : 1); ++i) {
obj = drm_gem_object_lookup(dev, file_priv,
mode_cmd->handles[i]);
if (obj == NULL) {
dev_dbg(dev->dev, "GEM object %u not found\n",
mode_cmd->handles[i]);
ret = -ENOENT;
goto error;
}
sfb->sobj[i] = to_shmob_gem_object(obj);
- }
I added multi-plane support the cma fb helper functions and it seems to work. But all other DRM drivers seem to assume that multi-plane formats still only have a single buffer, while yours seems to assume that there is a plane for each buffer. The exception is the Exynos driver, but it added a new set of formats which are identical to the other formats, but use one buffer per plane.
So I'm not sure how to implement this correctly in a generic fashion.
This post by Ville Syrjälä seems to explain it quite well: http://lists.freedesktop.org/archives/dri-devel/2012-April/021044.html
Hi Lars-Peter,
On Wednesday 30 May 2012 17:10:00 Lars-Peter Clausen wrote:
On 05/30/2012 04:45 PM, Lars-Peter Clausen wrote:
On 05/30/2012 02:32 PM, Laurent Pinchart wrote:
[...]
- for (i = 0; i < (format->yuv ? 2 : 1); ++i) {
obj = drm_gem_object_lookup(dev, file_priv,
mode_cmd->handles[i]);
if (obj == NULL) {
dev_dbg(dev->dev, "GEM object %u not found\n",
mode_cmd->handles[i]);
ret = -ENOENT;
goto error;
}
sfb->sobj[i] = to_shmob_gem_object(obj);
- }
I added multi-plane support the cma fb helper functions and it seems to work. But all other DRM drivers seem to assume that multi-plane formats still only have a single buffer, while yours seems to assume that there is a plane for each buffer. The exception is the Exynos driver, but it added a new set of formats which are identical to the other formats, but use one buffer per plane.
So I'm not sure how to implement this correctly in a generic fashion.
This post by Ville Syrjälä seems to explain it quite well: http://lists.freedesktop.org/archives/dri-devel/2012-April/021044.html
Do you plan to post a v3 of your "DRM: Add DRM kms/fb cma helper" patch in the near future ?
On 06/27/2012 02:40 PM, Laurent Pinchart wrote:
Hi Lars-Peter,
[...]
Do you plan to post a v3 of your "DRM: Add DRM kms/fb cma helper" patch in the near future ?
Hi Laurent,
I don't think there have been any changes since v2 except for adjusting to the renamed functions in Sascha's patch. I'll send a v3 tomorrow.
I think it makes most sense if you carry these two patches (Sascha's and mine) as part of your "Renesas SH Mobile DRM driver" series, so reviewers can see it in context.
- Lars
Hi Lars-Peter,
On Wednesday 27 June 2012 22:06:54 Lars-Peter Clausen wrote:
On 06/27/2012 02:40 PM, Laurent Pinchart wrote:
Hi Lars-Peter,
[...]
Do you plan to post a v3 of your "DRM: Add DRM kms/fb cma helper" patch in the near future ?
Hi Laurent,
I don't think there have been any changes since v2 except for adjusting to the renamed functions in Sascha's patch. I'll send a v3 tomorrow.
OK, thanks.
As Ville pointed out, we need to validate the offsets and make sure the frame buffer fits in the GEM object(s). I don't think there's code in your patch to do so, have you thought about where/how to implement that ?
I think it makes most sense if you carry these two patches (Sascha's and mine) as part of your "Renesas SH Mobile DRM driver" series, so reviewers can see it in context.
OK I'll do that.
On 06/27/2012 10:27 PM, Laurent Pinchart wrote:
Hi Lars-Peter,
On Wednesday 27 June 2012 22:06:54 Lars-Peter Clausen wrote:
On 06/27/2012 02:40 PM, Laurent Pinchart wrote:
Hi Lars-Peter,
[...]
Do you plan to post a v3 of your "DRM: Add DRM kms/fb cma helper" patch in the near future ?
Hi Laurent,
I don't think there have been any changes since v2 except for adjusting to the renamed functions in Sascha's patch. I'll send a v3 tomorrow.
OK, thanks.
As Ville pointed out, we need to validate the offsets and make sure the frame buffer fits in the GEM object(s). I don't think there's code in your patch to do so, have you thought about where/how to implement that ?
I think part of it needs to be done in the driver code, since the the generic code does not the specific buffer layout requirements imposed by the hardware. We can probably add some basic sanity checks for the buffer size though.
- Lars
On Wed, May 30, 2012 at 02:32:59PM +0200, Laurent Pinchart wrote:
The SH Mobile LCD controller (LCDC) DRM driver supports the main graphics plane in RGB and YUV formats, as well as the overlay planes (in alpha-blending mode only).
Only flat panel outputs using the parallel interface are supported. Support for SYS panels, HDMI and DSI is currently not implemented.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
[...]
+int shmob_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{
- struct drm_file *priv = filp->private_data;
- struct drm_device *dev = priv->minor->dev;
- struct drm_gem_mm *mm = dev->mm_private;
- struct shmob_drm_gem_object *sobj;
- struct drm_local_map *map;
- struct drm_gem_object *obj;
- struct drm_hash_item *hash;
- pgprot_t prot;
- int ret;
- if (drm_device_is_unplugged(dev))
return -ENODEV;
- mutex_lock(&dev->struct_mutex);
- if (drm_ht_find_item(&mm->offset_hash, vma->vm_pgoff, &hash)) {
ret = -EINVAL;
goto out_unlock;
- }
- map = drm_hash_entry(hash, struct drm_map_list, hash)->map;
- if (!map ||
((map->flags & _DRM_RESTRICTED) && !capable(CAP_SYS_ADMIN))) {
ret = -EPERM;
goto out_unlock;
- }
- /* Check for valid size. */
- if (map->size < vma->vm_end - vma->vm_start) {
ret = -EINVAL;
goto out_unlock;
- }
- obj = map->handle;
- if (!obj->dev->driver->gem_vm_ops) {
ret = -EINVAL;
goto out_unlock;
- }
- sobj = to_shmob_gem_object(obj);
- prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
- ret = remap_pfn_range(vma, vma->vm_start, sobj->dma >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, prot);
- if (ret < 0)
goto out_unlock;
- vma->vm_flags |= VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND;
- vma->vm_ops = obj->dev->driver->gem_vm_ops;
- vma->vm_private_data = obj;
- vma->vm_page_prot = prot;
- /* Take a ref for this mapping of the object, so that the fault
* handler can dereference the mmap offset's pointer to the object.
* This reference is cleaned up by the corresponding vm_close
* (which should happen whether the vma was created by this call, or
* by a vm_open due to mremap or partial unmap or whatever).
*/
- drm_gem_object_reference(obj);
- drm_vm_open_locked(dev, vma);
+out_unlock:
- mutex_unlock(&dev->struct_mutex);
- return ret;
+}
I just noticed this function is nearly a copy of drm_gem_mmap except for the the additional remap_pfn_range here. Would it make sense to turn drm_gem_mmap into a wrapper around a drm_gem_mmap_locked, call this one from here and add the remap_pfn_range?
If yes, I would do so in my cma gem helper patch.
Sascha
Hi Sascha,
On Wednesday 30 May 2012 18:40:56 Sascha Hauer wrote:
On Wed, May 30, 2012 at 02:32:59PM +0200, Laurent Pinchart wrote:
The SH Mobile LCD controller (LCDC) DRM driver supports the main graphics plane in RGB and YUV formats, as well as the overlay planes (in alpha-blending mode only).
Only flat panel outputs using the parallel interface are supported. Support for SYS panels, HDMI and DSI is currently not implemented.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
[...]
+int shmob_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{
- struct drm_file *priv = filp->private_data;
- struct drm_device *dev = priv->minor->dev;
- struct drm_gem_mm *mm = dev->mm_private;
- struct shmob_drm_gem_object *sobj;
- struct drm_local_map *map;
- struct drm_gem_object *obj;
- struct drm_hash_item *hash;
- pgprot_t prot;
- int ret;
- if (drm_device_is_unplugged(dev))
return -ENODEV;
- mutex_lock(&dev->struct_mutex);
- if (drm_ht_find_item(&mm->offset_hash, vma->vm_pgoff, &hash)) {
ret = -EINVAL;
goto out_unlock;
- }
- map = drm_hash_entry(hash, struct drm_map_list, hash)->map;
- if (!map ||
((map->flags & _DRM_RESTRICTED) && !capable(CAP_SYS_ADMIN))) {
ret = -EPERM;
goto out_unlock;
- }
- /* Check for valid size. */
- if (map->size < vma->vm_end - vma->vm_start) {
ret = -EINVAL;
goto out_unlock;
- }
- obj = map->handle;
- if (!obj->dev->driver->gem_vm_ops) {
ret = -EINVAL;
goto out_unlock;
- }
- sobj = to_shmob_gem_object(obj);
- prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
- ret = remap_pfn_range(vma, vma->vm_start, sobj->dma >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, prot);
- if (ret < 0)
goto out_unlock;
- vma->vm_flags |= VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND;
- vma->vm_ops = obj->dev->driver->gem_vm_ops;
- vma->vm_private_data = obj;
- vma->vm_page_prot = prot;
- /* Take a ref for this mapping of the object, so that the fault
* handler can dereference the mmap offset's pointer to the object.
* This reference is cleaned up by the corresponding vm_close
* (which should happen whether the vma was created by this call, or
* by a vm_open due to mremap or partial unmap or whatever).
*/
- drm_gem_object_reference(obj);
- drm_vm_open_locked(dev, vma);
+out_unlock:
- mutex_unlock(&dev->struct_mutex);
- return ret;
+}
I just noticed this function is nearly a copy of drm_gem_mmap except for the the additional remap_pfn_range here. Would it make sense to turn drm_gem_mmap into a wrapper around a drm_gem_mmap_locked, call this one from here and add the remap_pfn_range?
If yes, I would do so in my cma gem helper patch.
Seems you've already done so :-) I don't think locking is an issue, but as I mentioned in my review I don't know with 100% certainty what dev->struct_mutex covers.
dri-devel@lists.freedesktop.org