Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
/*
* Copyright (C) 2014 Endless Mobile
*
* 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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_flip_work.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_rect.h>
#include <drm/meson_drm.h>
#include "meson_cvbs.h"
#include "meson_hdmi.h"
#include "meson_modes.h"
#include "meson_priv.h"
#include <mach/am_regs.h>
#include <mach/irqs.h>
#include <linux/amlogic/vout/vout_notify.h>
#include <linux/amlogic/aml_gpio_consumer.h>
#include <linux/amlogic/hdmi_tx/hdmi_info_global.h>
#include <linux/amlogic/hdmi_tx/hdmi_tx_module.h>
enum meson_connectors {
MESON_CONNECTORS_HDMI = 0x1,
MESON_CONNECTORS_CVBS_NTSC = 0x2,
MESON_CONNECTORS_CVBS_PAL = 0x4,
};
static char enabled_connectors = 0;
module_param(enabled_connectors, byte, S_IRUGO | S_IWUSR);
#define DRIVER_NAME "meson"
#define DRIVER_DESC "Amlogic Meson DRM driver"
/* For debugging the driver, sometimes it helps to turn off fbdev
* to make things simpler. */
#define NO_FBDEV 0
/* Canvas configuration. */
enum meson_canvas_wrap {
MESON_CANVAS_WRAP_NONE = 0x00,
MESON_CANVAS_WRAP_X = 0x01,
MESON_CANVAS_WRAP_Y = 0x02,
};
enum meson_canvas_blkmode {
MESON_CANVAS_BLKMODE_LINEAR = 0x00,
MESON_CANVAS_BLKMODE_32x32 = 0x01,
MESON_CANVAS_BLKMODE_64x64 = 0x02,
};
/* Set up a canvas. */
static void canvas_setup(uint32_t canvas_index,
uint32_t addr,
uint32_t stride, uint32_t height,
enum meson_canvas_wrap wrap,
enum meson_canvas_blkmode blkmode)
{
CANVAS_WRITE(DC_CAV_LUT_DATAL,
(((addr + 7) >> 3)) |
(((stride + 7) >> 3) << CANVAS_WIDTH_LBIT));
CANVAS_WRITE(DC_CAV_LUT_DATAH,
((((stride + 7) >> 3) >> CANVAS_WIDTH_LWID) << CANVAS_WIDTH_HBIT) |
(height << CANVAS_HEIGHT_BIT) |
(wrap << 22) |
(blkmode << CANVAS_BLKMODE_BIT));
CANVAS_WRITE(DC_CAV_LUT_ADDR, CANVAS_LUT_WR_EN | canvas_index);
/* Force a read-back to make sure everything is flushed. */
CANVAS_READ(DC_CAV_LUT_DATAH);
}
/* CRTC definition */
enum meson_underscan_type {
UNDERSCAN_OFF,
UNDERSCAN_ON,
};
struct meson_crtc {
struct drm_crtc base;
struct drm_pending_vblank_event *event;
struct drm_property *prop_underscan;
struct drm_property *prop_underscan_hborder;
struct drm_property *prop_underscan_vborder;
enum meson_underscan_type underscan_type;
int underscan_hborder;
int underscan_vborder;
spinlock_t irq_lock;
};
#define to_meson_crtc(x) container_of(x, struct meson_crtc, base)
/* Plane */
enum osd_w0_bitflags {
OSD_ENDIANNESS_BE = (0x00 << 15),
OSD_ENDIANNESS_LE = (0x01 << 15),
OSD_BLK_MODE_422 = (0x03 << 8),
OSD_BLK_MODE_16 = (0x04 << 8),
OSD_BLK_MODE_32 = (0x05 << 8),
OSD_BLK_MODE_24 = (0x07 << 8),
OSD_OUTPUT_COLOR_YUV = (0x00 << 7),
OSD_OUTPUT_COLOR_RGB = (0x01 << 7),
OSD_COLOR_MATRIX_32_RGBA = (0x00 << 2),
OSD_COLOR_MATRIX_32_ARGB = (0x01 << 2),
OSD_COLOR_MATRIX_32_ABGR = (0x02 << 2),
OSD_COLOR_MATRIX_32_BGRA = (0x03 << 2),
OSD_INTERLACE_ENABLED = (0x01 << 1),
OSD_INTERLACE_ODD = (0x01 << 0),
OSD_INTERLACE_EVEN = (0x00 << 0),
};
/* Dumb metaprogramming, should replace with something better. */
#define OSD_REGISTERS \
M(CTRL_STAT) \
M(BLK0_CFG_W0) \
M(BLK0_CFG_W1) \
M(BLK0_CFG_W2) \
M(BLK0_CFG_W3) \
M(BLK0_CFG_W4)
struct osd_plane_def {
bool uses_scaler;
bool compensate_for_scaler;
uint32_t canvas_index;
uint32_t vpp_misc_postblend;
struct {
#define M(n) uint32_t n;
OSD_REGISTERS
#undef M
} reg;
};
struct osd_plane_registers {
#define M(n) uint32_t n;
OSD_REGISTERS
#undef M
};
enum meson_interlacing_strategy {
/* We don't require interlacing -- scan as progressive. */
MESON_INTERLACING_STRATEGY_NONE,
/* We are interlacing out this plane using the OSD interlacer. */
MESON_INTERLACING_STRATEGY_OSD,
/* We are interlacing out this plane using the HW scaler, so
* scan this out as progressive. */
MESON_INTERLACING_STRATEGY_SCALER,
};
struct meson_plane {
struct drm_plane base;
struct osd_plane_def *def;
/* These are shadow registers that are updated
* at vblank time. The various atomic_commit
* functions set these and we copy them into the
* real set of mapped registers at runtime. */
struct osd_plane_registers reg;
enum meson_interlacing_strategy interlacing_strategy;
bool fb_changed;
bool visible;
};
#define to_meson_plane(x) container_of(x, struct meson_plane, base)
/* XXX: This is super gross. Figure a better way to do this. */
static bool try_adjust_cvbs_hack_mode(struct drm_plane_state *state,
struct drm_rect *output,
int w, int h)
{
if (state->crtc_w == CVBS_HACK_MODE_SIZE(w) &&
state->crtc_h == CVBS_HACK_MODE_SIZE(h)) {
int hborder = w / CVBS_HACK_MODE_OVERSCAN_PERCENT;
int vborder = h / CVBS_HACK_MODE_OVERSCAN_PERCENT;
output->x1 = hborder;
output->x2 = w - hborder;
output->y1 = vborder;
output->y2 = h - vborder;
return true;
} else {
return false;
}
}
static void adjust_cvbs_hack_mode(struct drm_plane_state *state,
struct drm_rect *output)
{
if (!(state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE))
return;
if (try_adjust_cvbs_hack_mode(state, output, 720, 480))
return;
if (try_adjust_cvbs_hack_mode(state, output, 720, 576))
return;
}
static bool get_scaler_rects(struct drm_crtc *crtc,
struct drm_rect *input,
struct drm_rect *output)
{
struct meson_crtc *meson_crtc = to_meson_crtc(crtc);
struct drm_plane *plane = crtc->primary;
struct drm_plane_state *state = plane->state;
struct meson_plane *meson_plane = to_meson_plane(plane);
bool interlace = (meson_plane->interlacing_strategy == MESON_INTERLACING_STRATEGY_SCALER);
input->x1 = 0;
input->y1 = 0;
input->x2 = state->crtc_w;
input->y2 = state->crtc_h;
*output = *input;
adjust_cvbs_hack_mode(state, output);
if (meson_crtc->underscan_type == UNDERSCAN_ON) {
int hborder = meson_crtc->underscan_hborder;
int vborder = meson_crtc->underscan_vborder;
if (interlace)
vborder /= 2;
if (hborder != 0) {
output->x1 += hborder;
output->x2 -= hborder;
}
if (vborder != 0) {
output->y1 += vborder;
output->y2 -= vborder;
}
}
return (!drm_rect_equals(input, output));
}
/* Scales from the range a1..a2 to b1..b2 */
static inline int scale_into(int v, int a1, int a2, int b1, int b2)
{
return ((v - a1) * (b2 - b1) / (a2 - a1)) + b1;
}
static inline void scale_rect_into(struct drm_rect *dest,
struct drm_rect *input,
struct drm_rect *output)
{
int offs;
offs = scale_into(dest->x1, input->x1, input->x2, output->x1, output->x2) - dest->x1;
dest->x1 += offs;
dest->x2 += offs;
offs = scale_into(dest->y1, input->y1, input->y2, output->y1, output->y2) - dest->y1;
dest->y1 += offs;
dest->y2 += offs;
}
static int meson_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct drm_rect src = {
.x1 = state->src_x,
.y1 = state->src_y,
.x2 = state->src_x + state->src_w,
.y2 = state->src_y + state->src_h,
};
struct drm_rect dest = {
.x1 = state->crtc_x,
.y1 = state->crtc_y,
.x2 = state->crtc_x + state->crtc_w,
.y2 = state->crtc_y + state->crtc_h,
};
if (state->fb) {
int ret;
ret = drm_rect_calc_hscale(&src, &dest, DRM_PLANE_HELPER_NO_SCALING, DRM_PLANE_HELPER_NO_SCALING);
if (ret < 0)
return ret;
ret = drm_rect_calc_vscale(&src, &dest, DRM_PLANE_HELPER_NO_SCALING, DRM_PLANE_HELPER_NO_SCALING);
if (ret < 0)
return ret;
}
return 0;
}
/* Takes a fixed 16.16 number and converts it to integer. */
static inline int64_t fixed16_to_int(int64_t value)
{
return value >> 16;
}
static void meson_plane_atomic_update(struct drm_plane *plane, struct drm_plane_state *old_state)
{
struct meson_plane *meson_plane = to_meson_plane(plane);
struct drm_plane_state *state = plane->state;
struct meson_crtc *meson_crtc = to_meson_crtc(state->crtc);
struct drm_rect src = {
.x1 = (state->src_x),
.y1 = (state->src_y),
.x2 = (state->src_x + state->src_w),
.y2 = (state->src_y + state->src_h),
};
struct drm_rect dest = {
.x1 = state->crtc_x,
.y1 = state->crtc_y,
.x2 = state->crtc_x + state->crtc_w,
.y2 = state->crtc_y + state->crtc_h,
};
struct drm_rect clip = {};
bool is_scaling;
unsigned long flags;
if (state->fb) {
struct drm_rect input, output;
is_scaling = get_scaler_rects(state->crtc, &input, &output);
if (meson_plane->def->compensate_for_scaler && is_scaling)
scale_rect_into(&dest, &input, &output);
if (meson_plane->def->uses_scaler)
clip = input;
else
clip = output;
if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) {
clip.y1 /= 2;
clip.y2 /= 2;
dest.y1 /= 2;
dest.y2 /= 2;
}
meson_plane->visible = drm_rect_clip_scaled(&src, &dest, &clip,
DRM_PLANE_HELPER_NO_SCALING,
DRM_PLANE_HELPER_NO_SCALING);
} else {
meson_plane->visible = false;
}
if (meson_plane->visible) {
/* If we're interlacing, then figure out what strategy we're
* going to use. */
if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) {
/* If this plane is going to use the vertical scaler, then scan it out as
* progressive, as the scaler will take care of interlacing for us. */
if (meson_plane->def->uses_scaler && is_scaling)
meson_plane->interlacing_strategy = MESON_INTERLACING_STRATEGY_SCALER;
else
meson_plane->interlacing_strategy = MESON_INTERLACING_STRATEGY_OSD;
} else {
meson_plane->interlacing_strategy = MESON_INTERLACING_STRATEGY_NONE;
}
if (state->fb != old_state->fb)
meson_plane->fb_changed = true;
spin_lock_irqsave(&meson_crtc->irq_lock, flags);
/* Enable OSD and BLK0. */
meson_plane->reg.CTRL_STAT = ((1 << 21) | /* Enable OSD */
(0xFF << 12) | /* Alpha is 0xFF */
(1 << 0) /* Enable BLK0 */);
/* Set up BLK0 to point to the right canvas */
meson_plane->reg.BLK0_CFG_W0 = ((meson_plane->def->canvas_index << 16) |
OSD_ENDIANNESS_LE | OSD_BLK_MODE_32 | OSD_OUTPUT_COLOR_RGB | OSD_COLOR_MATRIX_32_ARGB);
if (meson_plane->interlacing_strategy == MESON_INTERLACING_STRATEGY_OSD)
meson_plane->reg.BLK0_CFG_W0 |= OSD_INTERLACE_ENABLED;
/* The format of these registers is (x2 << 16 | x1), where x2 is exclusive.
* e.g. +30x1920 would be (1949 << 16) | 30. */
meson_plane->reg.BLK0_CFG_W1 = ((fixed16_to_int(src.x2) - 1) << 16) | fixed16_to_int(src.x1);
meson_plane->reg.BLK0_CFG_W2 = ((fixed16_to_int(src.y2) - 1) << 16) | fixed16_to_int(src.y1);
meson_plane->reg.BLK0_CFG_W3 = ((dest.x2 - 1) << 16) | dest.x1;
meson_plane->reg.BLK0_CFG_W4 = ((dest.y2 - 1) << 16) | dest.y1;
spin_unlock_irqrestore(&meson_crtc->irq_lock, flags);
}
}
static const struct drm_plane_helper_funcs meson_plane_helper_funcs = {
.atomic_check = meson_plane_atomic_check,
.atomic_update = meson_plane_atomic_update,
};
static void meson_plane_destroy(struct drm_plane *plane)
{
drm_plane_helper_disable(plane);
drm_plane_cleanup(plane);
kfree(plane);
}
static const struct drm_plane_funcs meson_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = meson_plane_destroy,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
static const uint32_t supported_drm_formats[] = {
DRM_FORMAT_ARGB8888,
};
static struct drm_plane *meson_plane_create(struct drm_device *dev,
enum drm_plane_type type,
struct osd_plane_def *osd_plane_def)
{
struct meson_plane *meson_plane;
struct drm_plane *plane;
int ret;
meson_plane = kzalloc(sizeof(*meson_plane), GFP_KERNEL);
if (!meson_plane) {
ret = -ENOMEM;
goto fail;
}
plane = &meson_plane->base;
meson_plane->def = osd_plane_def;
drm_universal_plane_init(dev, plane, 0xFF,
&meson_plane_funcs,
supported_drm_formats,
ARRAY_SIZE(supported_drm_formats),
type);
drm_plane_helper_add(plane, &meson_plane_helper_funcs);
return plane;
fail:
return ERR_PTR(ret);
}
/* CRTC */
static void meson_crtc_destroy(struct drm_crtc *crtc)
{
struct meson_crtc *meson_crtc = to_meson_crtc(crtc);
drm_crtc_cleanup(crtc);
kfree(meson_crtc);
}
static struct drm_prop_enum_list underscan_enum_list[] =
{ { UNDERSCAN_OFF, "off" },
{ UNDERSCAN_ON, "on" },
};
static int meson_crtc_set_property(struct drm_crtc *crtc,
struct drm_property *property,
uint64_t val)
{
struct meson_crtc *meson_crtc = to_meson_crtc(crtc);
if (property == meson_crtc->prop_underscan) {
meson_crtc->underscan_type = val;
} else if (property == meson_crtc->prop_underscan_hborder) {
meson_crtc->underscan_hborder = val;
} else if (property == meson_crtc->prop_underscan_vborder) {
meson_crtc->underscan_vborder = val;
}
return 0;
}
static const struct drm_crtc_funcs meson_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.destroy = meson_crtc_destroy,
.reset = drm_atomic_helper_crtc_reset,
.page_flip = drm_atomic_helper_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.set_property = meson_crtc_set_property,
};
static void meson_crtc_dpms(struct drm_crtc *crtc, int mode)
{
hdmitx_dev_t *hdmitx_device = get_hdmitx_device();
if (mode == DRM_MODE_DPMS_OFF)
hdmitx_device->HWOp.CntlMisc(hdmitx_device, MISC_TMDS_PHY_OP, TMDS_PHY_DISABLE);
else
hdmitx_device->HWOp.CntlMisc(hdmitx_device, MISC_TMDS_PHY_OP, TMDS_PHY_ENABLE);
}
static void meson_crtc_prepare(struct drm_crtc *crtc)
{
meson_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
}
static void meson_crtc_disable(struct drm_crtc *crtc)
{
meson_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
}
static void meson_crtc_commit(struct drm_crtc *crtc)
{
}
static bool meson_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
/* nothing needed */
return true;
}
void meson_drm_set_vmode(vmode_t mode)
{
/* Call into aml's vout driver. */
/* AML's vout or HDMI driver really does not like when
* you change the mode to the same thing it already is
* for currently unknown reasons.
*
* Double-check that it hasn't changed before calling
* set_current_vmode and notifying the HDMI stack. */
/* XXX: Replace aml's vout driver with something sensible. */
if (mode == get_current_vmode())
return;
set_current_vmode(mode);
vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &mode);
}
static void meson_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
}
static void meson_crtc_load_lut(struct drm_crtc *crtc)
{
}
static int meson_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct meson_crtc *meson_crtc = to_meson_crtc(crtc);
/* If we're already page flipping and we get a new
* page flip, then reject. */
if (meson_crtc->event != NULL && state->event != NULL)
return -EINVAL;
return 0;
}
static void meson_crtc_atomic_flush(struct drm_crtc *crtc)
{
struct meson_crtc *meson_crtc = to_meson_crtc(crtc);
if (crtc->state->event != NULL) {
/* Make sure we only have one async page flip */
WARN_ON(meson_crtc->event != NULL);
meson_crtc->event = crtc->state->event;
}
}
static const struct drm_crtc_helper_funcs meson_crtc_helper_funcs = {
.dpms = meson_crtc_dpms,
.prepare = meson_crtc_prepare,
.disable = meson_crtc_disable,
.commit = meson_crtc_commit,
.mode_fixup = meson_crtc_mode_fixup,
.mode_set = drm_helper_crtc_mode_set,
.mode_set_base = drm_helper_crtc_mode_set_base,
.mode_set_nofb = meson_crtc_mode_set_nofb,
.load_lut = meson_crtc_load_lut,
.atomic_check = meson_crtc_atomic_check,
.atomic_flush = meson_crtc_atomic_flush,
};
/* Pick two canvases in the "user canvas" space that aren't
* likely to compete. */
static struct osd_plane_def osd_plane_defs[] = {
{
.uses_scaler = true,
.compensate_for_scaler = false,
.canvas_index = 0x4e,
.vpp_misc_postblend = VPP_OSD1_POSTBLEND,
{
#define M(n) .n = P_VIU_OSD1_##n ,
OSD_REGISTERS
#undef M
}
},
{
.uses_scaler = false,
.compensate_for_scaler = true,
.canvas_index = 0x4f,
.vpp_misc_postblend = VPP_OSD2_POSTBLEND,
{
#define M(n) .n = P_VIU_OSD2_##n ,
OSD_REGISTERS
#undef M
}
},
};
struct drm_crtc *meson_crtc_create(struct drm_device *dev)
{
struct meson_crtc *meson_crtc;
struct drm_crtc *crtc;
int ret;
struct drm_plane *primary_plane, *cursor_plane;
meson_crtc = kzalloc(sizeof(*meson_crtc), GFP_KERNEL);
if (!meson_crtc)
return NULL;
spin_lock_init(&meson_crtc->irq_lock);
primary_plane = meson_plane_create(dev,
DRM_PLANE_TYPE_PRIMARY,
&osd_plane_defs[0]);
cursor_plane = meson_plane_create(dev,
DRM_PLANE_TYPE_CURSOR,
&osd_plane_defs[1]);
crtc = &meson_crtc->base;
ret = drm_crtc_init_with_planes(dev, crtc,
primary_plane, cursor_plane,
&meson_crtc_funcs);
if (ret < 0)
goto fail;
drm_crtc_helper_add(crtc, &meson_crtc_helper_funcs);
meson_crtc->prop_underscan = drm_property_create_enum(dev, 0, "underscan", underscan_enum_list, ARRAY_SIZE(underscan_enum_list));
meson_crtc->prop_underscan_hborder = drm_property_create_range(dev, 0, "underscan hborder", 0, 128);
meson_crtc->prop_underscan_vborder = drm_property_create_range(dev, 0, "underscan vborder", 0, 128);
drm_object_attach_property(&meson_crtc->base.base, meson_crtc->prop_underscan, UNDERSCAN_OFF);
drm_object_attach_property(&meson_crtc->base.base, meson_crtc->prop_underscan_hborder, 0);
drm_object_attach_property(&meson_crtc->base.base, meson_crtc->prop_underscan_vborder, 0);
meson_crtc->underscan_type = UNDERSCAN_OFF;
meson_crtc->underscan_hborder = 0;
meson_crtc->underscan_vborder = 0;
return crtc;
fail:
meson_crtc_destroy(crtc);
return NULL;
}
/* DRM Driver */
struct meson_drm_private {
struct drm_crtc *crtc;
struct drm_fbdev_cma *fbdev;
struct drm_atomic_state *cleanup_state;
struct workqueue_struct *unref_wq;
struct drm_flip_work unref_work;
};
static void meson_fb_output_poll_changed(struct drm_device *dev)
{
#if !NO_FBDEV
struct meson_drm_private *priv = dev->dev_private;
drm_fbdev_cma_hotplug_event(priv->fbdev);
#endif
}
static void cleanup_atomic_state(struct drm_device *dev, struct drm_atomic_state *state)
{
drm_atomic_helper_cleanup_planes(dev, state);
drm_atomic_state_free(state);
}
static int meson_atomic_commit(struct drm_device *dev,
struct drm_atomic_state *state,
bool async)
{
struct meson_drm_private *priv = dev->dev_private;
int ret;
ret = drm_atomic_helper_prepare_planes(dev, state);
if (ret < 0)
return ret;
/*
* This is the point of no return - everything below never fails except
* when the hw goes bonghits. Which means we can commit the new state on
* the software side now.
*/
drm_atomic_helper_swap_state(dev, state);
drm_atomic_helper_commit_pre_planes(dev, state);
drm_atomic_helper_commit_planes(dev, state);
drm_atomic_helper_commit_post_planes(dev, state);
if (async) {
priv->cleanup_state = state;
} else {
drm_atomic_helper_wait_for_vblanks(dev, state);
cleanup_atomic_state(dev, state);
}
return 0;
}
static const struct drm_mode_config_funcs mode_config_funcs = {
.fb_create = drm_fb_cma_create,
.output_poll_changed = meson_fb_output_poll_changed,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = meson_atomic_commit,
};
static void meson_unref_worker(struct drm_flip_work *work, void *val)
{
struct drm_atomic_state *state = val;
struct drm_device *dev = state->dev;
cleanup_atomic_state(dev, state);
}
static void write_scaling_filter_coefs(const unsigned int *coefs,
bool is_horizontal)
{
int i;
aml_write_reg32(P_VPP_OSD_SCALE_COEF_IDX, (is_horizontal ? 1 : 0) << 8);
for (i = 0; i < 33; i++)
aml_write_reg32(P_VPP_OSD_SCALE_COEF, coefs[i]);
}
static unsigned int vpp_filter_coefs_4point_bspline[] = {
0x15561500, 0x14561600, 0x13561700, 0x12561800,
0x11551a00, 0x11541b00, 0x10541c00, 0x0f541d00,
0x0f531e00, 0x0e531f00, 0x0d522100, 0x0c522200,
0x0b522300, 0x0b512400, 0x0a502600, 0x0a4f2700,
0x094e2900, 0x084e2a00, 0x084d2b00, 0x074c2c01,
0x074b2d01, 0x064a2f01, 0x06493001, 0x05483201,
0x05473301, 0x05463401, 0x04453601, 0x04433702,
0x04423802, 0x03413a02, 0x03403b02, 0x033f3c02,
0x033d3d03
};
/* Configure the VPP to act like how we expect it to. Other drivers,
* like the ones included in U-Boot, might turn on weird features
* like the HW scaler or special planes. Reset the VPP to a sane mode
* that expects like we behave.
*/
static void reset_vpp(void)
{
/* Turn off the HW scalers -- U-Boot turns these on and we
* need to clear them to make things work. */
aml_clr_reg32_mask(P_VPP_OSD_SC_CTRL0, 1 << 3);
aml_clr_reg32_mask(P_VPP_OSD_VSC_CTRL0, 1 << 24);
aml_clr_reg32_mask(P_VPP_OSD_HSC_CTRL0, 1 << 22);
BUILD_BUG_ON(ARRAY_SIZE(vpp_filter_coefs_4point_bspline) != 33);
/* Write in the proper filter coefficients. */
write_scaling_filter_coefs(vpp_filter_coefs_4point_bspline, 0);
write_scaling_filter_coefs(vpp_filter_coefs_4point_bspline, 1);
/* Force all planes off -- U-Boot might configure them and
* we shouldn't have any stale planes. */
aml_clr_reg32_mask(P_VPP_MISC, VPP_OSD1_POSTBLEND | VPP_OSD2_POSTBLEND);
aml_clr_reg32_mask(P_VPP_MISC, VPP_VD1_POSTBLEND | VPP_VD2_POSTBLEND);
/* Turn on POSTBLEND. */
aml_set_reg32_mask(P_VPP_MISC, VPP_POSTBLEND_EN);
/* Put OSD2 (cursor) on top of OSD1. */
aml_set_reg32_mask(P_VPP_MISC, VPP_POST_FG_OSD2 | VPP_PRE_FG_OSD2);
/* In its default configuration, the display controller can be starved
* of memory bandwidth when the CPU and GPU are busy, causing scanout
* to sometimes get behind where it should be (with parts of the
* display appearing momentarily shifted to the right).
* Increase the priority and burst size of RAM access using the same
* values as Amlogic's driver. */
aml_set_reg32_mask(P_VIU_OSD1_FIFO_CTRL_STAT,
1 << 0 | /* Urgent DDR request priority */
3 << 10 /* Increase burst length from 24 to 64 */
);
aml_set_reg32_mask(P_VIU_OSD2_FIFO_CTRL_STAT,
1 << 0 | /* Urgent DDR request priority */
3 << 10 /* Increase burst length from 24 to 64 */
);
/* Increase the number of lines that the display controller waits
* after vsync before starting RAM access. This gives the vsync
* interrupt handler more time to update the registers, avoiding
* visual glitches. */
aml_set_reg32_bits(P_VIU_OSD1_FIFO_CTRL_STAT, 12, 5, 5);
aml_set_reg32_bits(P_VIU_OSD2_FIFO_CTRL_STAT, 12, 5, 5);
}
static ssize_t meson_get_underscan_hborder(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
struct meson_drm_private *priv = drm_dev->dev_private;
struct meson_crtc *meson_crtc = to_meson_crtc(priv->crtc);
return snprintf(buf, PAGE_SIZE, "%d\n", meson_crtc->underscan_hborder);
}
static ssize_t meson_set_underscan_hborder(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
struct meson_drm_private *priv = drm_dev->dev_private;
struct meson_crtc *meson_crtc = to_meson_crtc(priv->crtc);
sscanf(buf, "%d", &meson_crtc->underscan_hborder);
meson_crtc->underscan_type = UNDERSCAN_ON;
return size;
}
static DEVICE_ATTR(underscan_hborder, S_IRUGO | S_IWUGO, meson_get_underscan_hborder, meson_set_underscan_hborder);
static ssize_t meson_get_underscan_vborder(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
struct meson_drm_private *priv = drm_dev->dev_private;
struct meson_crtc *meson_crtc = to_meson_crtc(priv->crtc);
return snprintf(buf, PAGE_SIZE, "%d\n", meson_crtc->underscan_vborder);
}
static ssize_t meson_set_underscan_vborder(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
struct meson_drm_private *priv = drm_dev->dev_private;
struct meson_crtc *meson_crtc = to_meson_crtc(priv->crtc);
sscanf(buf, "%d", &meson_crtc->underscan_vborder);
meson_crtc->underscan_type = UNDERSCAN_ON;
return size;
}
static DEVICE_ATTR(underscan_vborder, S_IRUGO | S_IWUGO, meson_get_underscan_vborder, meson_set_underscan_vborder);
static void read_cvbs_switch_gpio(struct platform_device *pdev)
{
const char *str;
int ret;
int gpionr;
ret = of_property_read_string(pdev->dev.of_node, "cvbs_pal_gpio", &str);
if (ret == 0) {
gpionr = amlogic_gpio_name_map_num(str);
amlogic_gpio_request_one(gpionr, GPIOF_IN, "meson-drm pal");
amlogic_set_pull_up_down(gpionr, 1, "meson-drm pal");
if (amlogic_get_value(gpionr, "meson-drm pal")) {
dev_info(&pdev->dev, "Enable CVBS/PAL from switch\n");
enabled_connectors |= MESON_CONNECTORS_CVBS_PAL;
}
}
ret = of_property_read_string(pdev->dev.of_node, "cvbs_ntsc_gpio", &str);
if (ret == 0) {
gpionr = amlogic_gpio_name_map_num(str);
amlogic_gpio_request_one(gpionr, GPIOF_IN, "meson-drm ntsc");
amlogic_set_pull_up_down(gpionr, 1, "meson-drm ntsc");
if (amlogic_get_value(gpionr, "meson-drm ntsc")) {
dev_info(&pdev->dev, "Enable CVBS/NTSC from switch\n");
enabled_connectors |= MESON_CONNECTORS_CVBS_NTSC;
}
}
}
static int meson_load(struct drm_device *dev, unsigned long flags)
{
struct platform_device *pdev = dev->platformdev;
struct meson_drm_private *priv;
int ret;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
reset_vpp();
platform_set_drvdata(pdev, dev);
dev->dev_private = priv;
drm_mode_config_init(dev);
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
/* Just a guess for now */
dev->mode_config.max_width = 9999;
dev->mode_config.max_height = 9999;
dev->mode_config.funcs = &mode_config_funcs;
priv->crtc = meson_crtc_create(dev);
if (enabled_connectors == 0) {
/* Default: HDMI enabled, CVBS enabled via switch */
enabled_connectors = MESON_CONNECTORS_HDMI;
//read_cvbs_switch_gpio(pdev);
}
meson_hdmi_connector_create(dev, !!(enabled_connectors & MESON_CONNECTORS_HDMI));
{
struct drm_display_mode *mode = drm_cvt_mode(dev,
CVBS_HACK_MODE_SIZE(720),
CVBS_HACK_MODE_SIZE(480),
60, false, true, false);
mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
meson_cvbs_connector_create(dev, !!(enabled_connectors & MESON_CONNECTORS_CVBS_NTSC), mode);
}
{
struct drm_display_mode *mode = drm_cvt_mode(dev,
CVBS_HACK_MODE_SIZE(720),
CVBS_HACK_MODE_SIZE(576),
50, false, true, false);
mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
meson_cvbs_connector_create(dev, !!(enabled_connectors & MESON_CONNECTORS_CVBS_PAL), mode);
}
ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
if (ret < 0) {
/* XXX: Don't leak memory. */
return ret;
}
/* set vout mode at startup to prevent the rest of
* amlogic's drivers from crashing... */
meson_drm_set_vmode(VMODE_1080P);
ret = drm_flip_work_init(&priv->unref_work, 16,
"unref", meson_unref_worker);
priv->unref_wq = alloc_ordered_workqueue("meson", 0);
drm_irq_install(dev, INT_VIU_VSYNC);
drm_mode_config_reset(dev);
drm_kms_helper_poll_init(dev);
#if !NO_FBDEV
priv->fbdev = drm_fbdev_cma_init(dev, 32,
dev->mode_config.num_crtc,
dev->mode_config.num_connector);
#endif
device_create_file(dev->dev, &dev_attr_underscan_hborder);
device_create_file(dev->dev, &dev_attr_underscan_vborder);
return 0;
}
static int meson_unload(struct drm_device *dev)
{
struct meson_drm_private *priv = dev->dev_private;
drm_kms_helper_poll_fini(dev);
drm_mode_config_cleanup(dev);
drm_flip_work_cleanup(&priv->unref_work);
kfree(dev->dev_private);
dev->dev_private = NULL;
return 0;
}
static int meson_open(struct drm_device *dev, struct drm_file *file)
{
struct meson_drm_session_data *session_data;
session_data = kzalloc(sizeof(*session_data), GFP_KERNEL);
if (!session_data)
return -ENOMEM;
mutex_init(&session_data->mutex);
file->driver_priv = session_data;
return 0;
}
static void meson_postclose(struct drm_device *dev, struct drm_file *file)
{
struct meson_drm_session_data *session_data = file->driver_priv;
if (session_data)
mutex_destroy(&session_data->mutex);
kfree(session_data);
}
static void meson_lastclose(struct drm_device *dev)
{
#if !NO_FBDEV
struct meson_drm_private *priv = dev->dev_private;
drm_fbdev_cma_restore_mode(priv->fbdev);
#endif
}
static int meson_enable_vblank(struct drm_device *dev, int crtc)
{
return 0;
}
static void meson_disable_vblank(struct drm_device *dev, int crtc)
{
}
static void meson_crtc_send_vblank_event(struct drm_crtc *crtc)
{
struct meson_crtc *meson_crtc = to_meson_crtc(crtc);
if (meson_crtc->event) {
spin_lock(&crtc->dev->event_lock);
drm_send_vblank_event(crtc->dev,
drm_crtc_index(crtc),
meson_crtc->event);
meson_crtc->event = NULL;
spin_unlock(&crtc->dev->event_lock);
}
}
static void update_scaler(struct drm_crtc *crtc)
{
struct meson_plane *meson_plane = to_meson_plane(crtc->primary);
struct drm_plane_state *state = crtc->primary->state;
struct drm_rect input, output;
if (!meson_plane->visible)
return;
if (!state)
return;
if (get_scaler_rects(crtc, &input, &output)) {
bool interlace = (meson_plane->interlacing_strategy == MESON_INTERLACING_STRATEGY_SCALER);
if (interlace) {
output.y1 /= 2;
output.y2 /= 2;
}
/* Basic scaler config */
aml_write_reg32(P_VPP_OSD_SC_CTRL0,
(1 << 3) /* Enable scaler */ |
(0 << 2) /* Select OSD1 */);
aml_write_reg32(P_VPP_OSD_SCI_WH_M1,
((drm_rect_width(&input) - 1) << 16) | (drm_rect_height(&input) - 1));
aml_write_reg32(P_VPP_OSD_SCO_H_START_END, ((output.x1) << 16) | (output.x2));
aml_write_reg32(P_VPP_OSD_SCO_V_START_END, ((output.y1) << 16) | (output.y2));
/* HSC */
if (input.x1 != output.x1 || input.x2 != output.x2) {
int hf_phase_step = ((drm_rect_width(&input) << 18) / drm_rect_width(&output));
aml_write_reg32(P_VPP_OSD_HSC_PHASE_STEP, hf_phase_step << 6);
aml_write_reg32(P_VPP_OSD_HSC_CTRL0,
(4 << 0) /* osd_hsc_bank_length */ |
(4 << 3) /* osd_hsc_ini_rcv_num0 */ |
(1 << 8) /* osd_hsc_rpt_p0_num0 */ |
(1 << 22) /* Enable horizontal scaler */);
} else {
aml_write_reg32(P_VPP_OSD_HSC_CTRL0, 0);
}
/* VSC */
if (input.y1 != output.y1 || input.y2 != output.y2) {
int vf_phase_step = ((drm_rect_height(&input) << 20) / drm_rect_height(&output));
aml_write_reg32(P_VPP_OSD_VSC_INI_PHASE, interlace ? (vf_phase_step >> 5) : 0);
aml_write_reg32(P_VPP_OSD_VSC_PHASE_STEP, vf_phase_step << 4);
aml_write_reg32(P_VPP_OSD_VSC_CTRL0,
(4 << 0) /* osd_vsc_bank_length */ |
(4 << 3) /* osd_vsc_top_ini_rcv_num0 */ |
(1 << 8) /* osd_vsc_top_rpt_p0_num0 */ |
(6 << 11) /* osd_vsc_bot_ini_rcv_num0 */ |
(2 << 16) /* osd_vsc_bot_rpt_p0_num0 */ |
((interlace ? 1 : 0) << 23) /* osd_prog_interlace */ |
(1 << 24) /* Enable vertical scaler */);
} else {
aml_write_reg32(P_VPP_OSD_VSC_CTRL0, 0);
}
} else {
aml_write_reg32(P_VPP_OSD_SC_CTRL0,
(0 << 3) /* Disable scaler */);
}
}
static void update_plane_shadow_registers(struct drm_plane *plane)
{
struct meson_plane *meson_plane = to_meson_plane(plane);
struct drm_plane_state *state = plane->state;
if (meson_plane->visible) {
if (meson_plane->fb_changed) {
struct drm_gem_cma_object *cma_bo;
cma_bo = drm_fb_cma_get_gem_obj(state->fb, 0);
/* Swap out the OSD canvas with the new addr. */
canvas_setup(meson_plane->def->canvas_index,
cma_bo->paddr,
state->fb->pitches[0],
state->fb->height,
MESON_CANVAS_WRAP_NONE,
MESON_CANVAS_BLKMODE_LINEAR);
meson_plane->fb_changed = false;
}
aml_set_reg32_mask(P_VPP_MISC, meson_plane->def->vpp_misc_postblend);
/* Copy the shadow registers into the real registers. */
#define M(n) aml_write_reg32(meson_plane->def->reg.n, meson_plane->reg.n);
OSD_REGISTERS
#undef M
} else {
aml_clr_reg32_mask(P_VPP_MISC, meson_plane->def->vpp_misc_postblend);
}
}
static void update_interlaced_field(struct drm_plane *plane)
{
struct meson_plane *meson_plane = to_meson_plane(plane);
if (meson_plane->interlacing_strategy != MESON_INTERLACING_STRATEGY_NONE) {
int field = aml_read_reg32(P_ENCI_INFO_READ) & (1 << 29);
meson_plane->reg.BLK0_CFG_W0 = ((meson_plane->reg.BLK0_CFG_W0 & ~0x01) |
(field ? OSD_INTERLACE_ODD : OSD_INTERLACE_EVEN));
}
}
static irqreturn_t meson_irq(int irq, void *arg)
{
struct drm_device *dev = arg;
struct meson_drm_private *priv = dev->dev_private;
struct meson_crtc *meson_crtc = to_meson_crtc(priv->crtc);
drm_handle_vblank(dev, 0);
meson_crtc_send_vblank_event(priv->crtc);
spin_lock(&meson_crtc->irq_lock);
update_interlaced_field(priv->crtc->primary);
update_interlaced_field(priv->crtc->cursor);
update_plane_shadow_registers(priv->crtc->primary);
update_plane_shadow_registers(priv->crtc->cursor);
update_scaler(priv->crtc);
spin_unlock(&meson_crtc->irq_lock);
if (priv->cleanup_state) {
drm_flip_work_queue(&priv->unref_work, priv->cleanup_state);
priv->cleanup_state = NULL;
drm_flip_work_commit(&priv->unref_work, priv->unref_wq);
}
return IRQ_HANDLED;
}
static void meson_gem_free_object(struct drm_gem_object *obj)
{
drm_gem_cma_free_object(obj);
}
static int meson_ioctl_create_with_ump(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct drm_meson_gem_create_with_ump *args = data;
struct drm_gem_cma_object *cma_obj;
unsigned int size;
DEFINE_DMA_ATTRS(dma_attrs);
/* UMP requires a page-aligned size for its buffers. */
size = PAGE_ALIGN (args->size);
/* All allocations currently contiguous, to be improved later. */
dma_set_attr(DMA_ATTR_FORCE_CONTIGUOUS, &dma_attrs);
/* We do not need kernel virtual addresses */
dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &dma_attrs);
if (args->flags & DRM_MESON_GEM_CREATE_WITH_UMP_FLAG_SCANOUT) {
/* No caching for scanout buffers */
dma_set_attr(DMA_ATTR_WRITE_COMBINE, &dma_attrs);
} else {
/* Other buffers are textures and caches can be enabled. */
WARN_ON(!(args->flags & DRM_MESON_GEM_CREATE_WITH_UMP_FLAG_TEXTURE));
dma_set_attr(DMA_ATTR_NON_CONSISTENT, &dma_attrs);
}
cma_obj = drm_gem_cma_create_with_handle(file, dev, size, &args->handle, &dma_attrs);
if (IS_ERR(cma_obj))
return PTR_ERR(cma_obj);
return PTR_ERR_OR_ZERO(cma_obj);
}
static const struct drm_ioctl_desc meson_ioctls[] = {
DRM_IOCTL_DEF_DRV(MESON_GEM_CREATE_WITH_UMP, meson_ioctl_create_with_ump, DRM_UNLOCKED|DRM_AUTH|DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(MESON_MSYNC, meson_ioctl_msync, DRM_UNLOCKED|DRM_AUTH|DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(MESON_GEM_SET_DOMAIN, meson_ioctl_set_domain, DRM_UNLOCKED|DRM_AUTH|DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(MESON_CACHE_OPERATIONS_CONTROL, meson_ioctl_cache_operations_control, DRM_UNLOCKED|DRM_AUTH|DRM_RENDER_ALLOW),
};
#ifdef CONFIG_DEBUG_FS
static struct drm_info_list meson_debugfs_list[] = {
{ "fb", drm_fb_cma_debugfs_show, 0 },
};
static int meson_debugfs_init(struct drm_minor *minor)
{
struct drm_device *dev = minor->dev;
int ret;
ret = drm_debugfs_create_files(meson_debugfs_list,
ARRAY_SIZE(meson_debugfs_list),
minor->debugfs_root, minor);
if (ret) {
dev_err(dev->dev, "could not install meson_debugfs_list\n");
return ret;
}
return ret;
}
static void meson_debugfs_cleanup(struct drm_minor *minor)
{
drm_debugfs_remove_files(meson_debugfs_list,
ARRAY_SIZE(meson_debugfs_list), minor);
}
#endif
static const struct file_operations 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,
.llseek = no_llseek,
.mmap = drm_gem_cma_mmap,
};
static struct drm_driver meson_driver = {
.driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME,
.load = meson_load,
.unload = meson_unload,
.open = meson_open,
.postclose = meson_postclose,
.lastclose = meson_lastclose,
.enable_vblank = meson_enable_vblank,
.disable_vblank = meson_disable_vblank,
.get_vblank_counter = drm_vblank_count,
.irq_handler = meson_irq,
.set_busid = drm_platform_set_busid,
.fops = &fops,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = "20141113",
.major = 1,
.minor = 0,
.gem_free_object = meson_gem_free_object,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import = drm_gem_prime_import,
.gem_prime_export = drm_gem_prime_export,
.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
.gem_prime_vmap = drm_gem_cma_prime_vmap,
.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
.gem_prime_mmap = drm_gem_cma_prime_mmap,
.dumb_create = drm_gem_cma_dumb_create,
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
.dumb_destroy = drm_gem_dumb_destroy,
.ioctls = meson_ioctls,
.num_ioctls = DRM_MESON_NUM_IOCTLS,
#ifdef CONFIG_DEBUG_FS
.debugfs_init = meson_debugfs_init,
.debugfs_cleanup = meson_debugfs_cleanup,
#endif
};
static int meson_pdev_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
/* No DT configuration? Bail. */
if (!node)
return -ENXIO;
return drm_platform_init(&meson_driver, pdev);
}
static int meson_pdev_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id dt_match[] = {
{ .compatible = "amlogic,meson8b-display" },
{}
};
MODULE_DEVICE_TABLE(of, dt_match);
static struct platform_driver meson_drm_platform_driver = {
.probe = meson_pdev_probe,
.remove = meson_pdev_remove,
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
.of_match_table = dt_match,
},
};
module_platform_driver(meson_drm_platform_driver);
MODULE_AUTHOR("Jasper St. Pierre <jstpierre@mecheye.net>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment