drm/msm/sde: add hardware driver support for writeback interface

Writeback block is used to output composited images to memory.
This patch adds support to the hardware driver layer such that
writeback hardware block can be controlled using hardware
abstraction api common across families of hardware.

Change-Id: I92a69f1d564ee87e3f040cbef5a0302468599440
Signed-off-by: Alan Kwong <akwong@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/sde/sde_formats.c b/drivers/gpu/drm/msm/sde/sde_formats.c
index ac14bad..5655a8a 100644
--- a/drivers/gpu/drm/msm/sde/sde_formats.c
+++ b/drivers/gpu/drm/msm/sde/sde_formats.c
@@ -124,6 +124,12 @@
 		true, 4, 0,
 		SDE_FETCH_LINEAR, 1),
 
+	INTERLEAVED_RGB_FMT(BGRX8888,
+		COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
+		C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
+		false, 4, 0,
+		SDE_FETCH_LINEAR, 1),
+
 	INTERLEAVED_RGB_FMT(XRGB8888,
 		COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
 		C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
index c2d8eac..7d0e908 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
@@ -154,6 +154,7 @@
  * @SDE_WB_UBWC_1_0,        Writeback Universal bandwidth compression 1.0
  *                          support
  * @SDE_WB_WBWC_1_5         UBWC 1.5 support
+ * @SDE_WB_YUV_CONFIG       Writeback supports output of YUV colorspace
  * @SDE_WB_MAX              maximum value
  */
 enum {
@@ -166,6 +167,7 @@
 	SDE_WB_DITHER,
 	SDE_WB_TRAFFIC_SHAPER,
 	SDE_WB_UBWC_1_0,
+	SDE_WB_YUV_CONFIG,
 	SDE_WB_MAX
 };
 
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c b/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c
index e0094bc..2f8bcc0 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c
@@ -53,10 +53,11 @@
 	(BIT(SDE_WB_LINE_MODE) | BIT(SDE_WB_BLOCK_MODE) |\
 	BIT(SDE_WB_CSC) | BIT(SDE_WB_CHROMA_DOWN) | BIT(SDE_WB_DOWNSCALE) |\
 	BIT(SDE_WB_DITHER) | BIT(SDE_WB_TRAFFIC_SHAPER) |\
-	BIT(SDE_WB_UBWC_1_0))
+	BIT(SDE_WB_UBWC_1_0) | BIT(SDE_WB_YUV_CONFIG))
 
 #define WB2_17X_MASK \
-	(BIT(SDE_WB_LINE_MODE) | BIT(SDE_WB_TRAFFIC_SHAPER))
+	(BIT(SDE_WB_LINE_MODE) | BIT(SDE_WB_TRAFFIC_SHAPER) |\
+	BIT(SDE_WB_YUV_CONFIG))
 
 #define DECIMATION_17X_MAX_H	4
 #define DECIMATION_17X_MAX_V	4
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_ctl.c b/drivers/gpu/drm/msm/sde/sde_hw_ctl.c
index 773e3d5..7528871 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_ctl.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_ctl.c
@@ -197,6 +197,21 @@
 	return 0;
 }
 
+static inline int sde_hw_ctl_get_bitmask_wb(struct sde_hw_ctl *ctx,
+		u32 *flushbits, enum sde_wb wb)
+{
+	switch (wb) {
+	case WB_0:
+	case WB_1:
+	case WB_2:
+		*flushbits |= BIT(16);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
 static inline int sde_hw_ctl_get_bitmask_cdm(struct sde_hw_ctl *ctx,
 		u32 *flushbits, enum sde_cdm cdm)
 {
@@ -364,6 +379,7 @@
 	ops->get_bitmask_dspp = sde_hw_ctl_get_bitmask_dspp;
 	ops->get_bitmask_intf = sde_hw_ctl_get_bitmask_intf;
 	ops->get_bitmask_cdm = sde_hw_ctl_get_bitmask_cdm;
+	ops->get_bitmask_wb = sde_hw_ctl_get_bitmask_wb;
 };
 
 struct sde_hw_ctl *sde_hw_ctl_init(enum sde_ctl idx,
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_ctl.h b/drivers/gpu/drm/msm/sde/sde_hw_ctl.h
index b5a675e..007cb80 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_ctl.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_ctl.h
@@ -118,6 +118,10 @@
 		u32 *flushbits,
 		enum sde_cdm blk);
 
+	int (*get_bitmask_wb)(struct sde_hw_ctl *ctx,
+		u32 *flushbits,
+		enum sde_wb blk);
+
 	void (*setup_blendstage)(struct sde_hw_ctl *ctx,
 		enum sde_lm lm,
 		struct sde_hw_stage_cfg *cfg);
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_mdss.h b/drivers/gpu/drm/msm/sde/sde_hw_mdss.h
index ada3aef..73cecbf 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_mdss.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_mdss.h
@@ -39,10 +39,9 @@
 #define PIPES_PER_STAGE			2
 
 #define SDE_FORMAT_FLAG_YUV		(1 << 0)
-#define SDE_FORMAT_FLAG_ROTATOR		(1 << 1)
 
 #define SDE_FORMAT_IS_YUV(X)		((X)->flag & SDE_FORMAT_FLAG_YUV)
-#define SDE_FORMAT_IS_ROTATOR(X)	((X)->flag & SDE_FORMAT_FLAG_ROTATOR)
+#define SDE_FORMAT_IS_LINEAR(X)		((X)->fetch_mode == SDE_FETCH_LINEAR)
 #define SDE_FORMAT_IS_UBWC(X)		((X)->fetch_mode == SDE_FETCH_UBWC)
 
 enum sde_mdp {
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_top.c b/drivers/gpu/drm/msm/sde/sde_hw_top.c
index 3c7d7f4..31d5cce 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_top.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_top.c
@@ -27,6 +27,11 @@
 #define UPPER_PIPE_CTRL                   0x3F0
 #define TE_LINE_INTERVAL                  0x3F4
 
+#define TRAFFIC_SHAPER_EN                 BIT(31)
+#define TRAFFIC_SHAPER_RD_CLIENT(num)     (0x030 + (num * 4))
+#define TRAFFIC_SHAPER_WR_CLIENT(num)     (0x060 + (num * 4))
+#define TRAFFIC_SHAPER_FIXPOINT_FACTOR    4
+
 static void sde_hw_setup_split_pipe_control(struct sde_hw_mdp *mdp,
 		struct split_pipe_cfg *cfg)
 {
@@ -65,10 +70,35 @@
 	SDE_REG_WRITE(c, SPLIT_DISPLAY_ENABLE, cfg->en & 0x1);
 }
 
+static void sde_hw_setup_traffic_shaper(struct sde_hw_mdp *mdp,
+		struct traffic_shaper_cfg *cfg)
+{
+	struct sde_hw_blk_reg_map *c = &mdp->hw;
+	u32 ts_control = 0;
+	u32 offset;
+	u64 bpc;
+
+	if (cfg->rd_client)
+		offset = TRAFFIC_SHAPER_RD_CLIENT(cfg->client_id);
+	else
+		offset = TRAFFIC_SHAPER_WR_CLIENT(cfg->client_id);
+
+	if (cfg->en) {
+		bpc = cfg->bpc_numer;
+		do_div(bpc, (cfg->bpc_denom >>
+					TRAFFIC_SHAPER_FIXPOINT_FACTOR));
+		ts_control = lower_32_bits(bpc) + 1;
+		ts_control |= TRAFFIC_SHAPER_EN;
+	}
+
+	SDE_REG_WRITE(c, offset, ts_control);
+}
+
 static void _setup_mdp_ops(struct sde_hw_mdp_ops *ops,
 		unsigned long cap)
 {
 	ops->setup_split_pipe = sde_hw_setup_split_pipe_control;
+	ops->setup_traffic_shaper = sde_hw_setup_traffic_shaper;
 }
 
 static const struct sde_mdp_cfg *_top_offset(enum sde_mdp mdp,
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_top.h b/drivers/gpu/drm/msm/sde/sde_hw_top.h
index 6c15651..a0c8bbe 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_top.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_top.h
@@ -20,6 +20,22 @@
 struct sde_hw_mdp;
 
 /**
+ * struct traffic_shaper_cfg: traffic shaper configuration
+ * @en        : enable/disable traffic shaper
+ * @rd_client : true if read client; false if write client
+ * @client_id : client identifier
+ * @bpc_denom : denominator of byte per clk
+ * @bpc_numer : numerator of byte per clk
+ */
+struct traffic_shaper_cfg {
+	bool en;
+	bool rd_client;
+	u32 client_id;
+	u32 bpc_denom;
+	u64 bpc_numer;
+};
+
+/**
  * struct split_pipe_cfg - pipe configuration for dual display panels
  * @en        : Enable/disable dual pipe confguration
  * @mode      : Panel interface mode
@@ -40,6 +56,7 @@
  * struct sde_hw_mdp_ops - interface to the MDP TOP Hw driver functions
  * Assumption is these functions will be called after clocks are enabled.
  * @setup_split_pipe : Programs the pipe control registers
+ * @setup_traffic_shaper : programs traffic shaper control
  */
 struct sde_hw_mdp_ops {
 	/** setup_split_pipe() : Regsiters are not double buffered, thisk
@@ -49,6 +66,14 @@
 	 */
 	void (*setup_split_pipe)(struct sde_hw_mdp *mdp,
 			struct split_pipe_cfg *p);
+
+	/**
+	 * setup_traffic_shaper() : Setup traffic shaper control
+	 * @mdp  : mdp top context driver
+	 * @cfg  : traffic shaper configuration
+	 */
+	void (*setup_traffic_shaper)(struct sde_hw_mdp *mdp,
+			struct traffic_shaper_cfg *cfg);
 };
 
 struct sde_hw_mdp {
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_wb.c b/drivers/gpu/drm/msm/sde/sde_hw_wb.c
index ef3a35a..ac6ff8c 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_wb.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_wb.c
@@ -14,6 +14,35 @@
 #include "sde_hwio.h"
 #include "sde_hw_catalog.h"
 #include "sde_hw_wb.h"
+#include "sde_formats.h"
+
+#define WB_DST_FORMAT			0x000
+#define WB_DST_OP_MODE			0x004
+#define WB_DST_PACK_PATTERN		0x008
+#define WB_DST0_ADDR			0x00C
+#define WB_DST1_ADDR			0x010
+#define WB_DST2_ADDR			0x014
+#define WB_DST3_ADDR			0x018
+#define WB_DST_YSTRIDE0			0x01C
+#define WB_DST_YSTRIDE1			0x020
+#define WB_DST_YSTRIDE1			0x020
+#define WB_DST_DITHER_BITDEPTH		0x024
+#define WB_DST_MATRIX_ROW0		0x030
+#define WB_DST_MATRIX_ROW1		0x034
+#define WB_DST_MATRIX_ROW2		0x038
+#define WB_DST_MATRIX_ROW3		0x03C
+#define WB_DST_WRITE_CONFIG		0x048
+#define WB_ROTATION_DNSCALER		0x050
+#define WB_ROTATOR_PIPE_DOWNSCALER	0x054
+#define WB_N16_INIT_PHASE_X_C03		0x060
+#define WB_N16_INIT_PHASE_X_C12		0x064
+#define WB_N16_INIT_PHASE_Y_C03		0x068
+#define WB_N16_INIT_PHASE_Y_C12		0x06C
+#define WB_OUT_SIZE			0x074
+#define WB_ALPHA_X_VALUE		0x078
+#define WB_CSC_BASE			0x260
+#define WB_DST_ADDR_SW_STATUS		0x2B0
+#define WB_CDP_CTRL			0x2B4
 
 static struct sde_wb_cfg *_wb_offset(enum sde_wb wb,
 		struct sde_mdss_cfg *m,
@@ -42,11 +71,93 @@
 static void sde_hw_wb_setup_outaddress(struct sde_hw_wb *ctx,
 		struct sde_hw_wb_cfg *data)
 {
+	struct sde_hw_blk_reg_map *c = &ctx->hw;
+
+	SDE_REG_WRITE(c, WB_DST0_ADDR, data->dest.plane_addr[0]);
+	SDE_REG_WRITE(c, WB_DST1_ADDR, data->dest.plane_addr[1]);
+	SDE_REG_WRITE(c, WB_DST2_ADDR, data->dest.plane_addr[2]);
+	SDE_REG_WRITE(c, WB_DST3_ADDR, data->dest.plane_addr[3]);
 }
 
 static void sde_hw_wb_setup_format(struct sde_hw_wb *ctx,
 		struct sde_hw_wb_cfg *data)
 {
+	struct sde_hw_blk_reg_map *c = &ctx->hw;
+	const struct sde_format *fmt = data->dest.format;
+	u32 dst_format, pattern, ystride0, ystride1, outsize, chroma_samp;
+	u32 write_config = 0;
+	u32 opmode = 0;
+	u32 dst_addr_sw = 0;
+	u32 cdp_settings = 0x0;
+
+	chroma_samp = fmt->chroma_sample;
+
+	dst_format = (chroma_samp << 23) |
+			(fmt->fetch_planes << 19) |
+			(fmt->bits[C3_ALPHA] << 6) |
+			(fmt->bits[C2_R_Cr] << 4) |
+			(fmt->bits[C1_B_Cb] << 2) |
+			(fmt->bits[C0_G_Y] << 0);
+
+	if (fmt->bits[C3_ALPHA] || fmt->alpha_enable) {
+		dst_format |= BIT(8); /* DSTC3_EN */
+		if (!fmt->alpha_enable)
+			dst_format |= BIT(14); /* DST_ALPHA_X */
+	}
+
+	if (SDE_FORMAT_IS_YUV(fmt) &&
+			(ctx->caps->features & BIT(SDE_WB_YUV_CONFIG)))
+		dst_format |= BIT(15);
+
+	pattern = (fmt->element[3] << 24) |
+			(fmt->element[2] << 16) |
+			(fmt->element[1] << 8)  |
+			(fmt->element[0] << 0);
+
+	dst_format |= (fmt->unpack_align_msb << 18) |
+			(fmt->unpack_tight << 17) |
+			((fmt->unpack_count - 1) << 12) |
+			((fmt->bpp - 1) << 9);
+
+	ystride0 = data->dest.plane_pitch[0] |
+			(data->dest.plane_pitch[1] << 16);
+	ystride1 = data->dest.plane_pitch[2] |
+			(data->dest.plane_pitch[3] << 16);
+	outsize = (data->dest.height << 16) | data->dest.width;
+
+	if (SDE_FORMAT_IS_UBWC(fmt)) {
+		opmode |= BIT(0);
+		dst_format |= BIT(31);
+		if (ctx->highest_bank_bit)
+			write_config |= (ctx->highest_bank_bit << 8);
+		if (fmt->base.pixel_format == DRM_FORMAT_RGB565)
+			write_config |= 0x8;
+	}
+
+	if (data->is_secure)
+		dst_addr_sw |= BIT(0);
+
+	SDE_REG_WRITE(c, WB_ALPHA_X_VALUE, 0xFF);
+	SDE_REG_WRITE(c, WB_DST_FORMAT, dst_format);
+	SDE_REG_WRITE(c, WB_DST_OP_MODE, opmode);
+	SDE_REG_WRITE(c, WB_DST_PACK_PATTERN, pattern);
+	SDE_REG_WRITE(c, WB_DST_YSTRIDE0, ystride0);
+	SDE_REG_WRITE(c, WB_DST_YSTRIDE1, ystride1);
+	SDE_REG_WRITE(c, WB_OUT_SIZE, outsize);
+	SDE_REG_WRITE(c, WB_DST_WRITE_CONFIG, write_config);
+	SDE_REG_WRITE(c, WB_DST_ADDR_SW_STATUS, dst_addr_sw);
+
+	/* Enable CDP */
+	cdp_settings = BIT(0);
+
+	if (!SDE_FORMAT_IS_LINEAR(fmt))
+		cdp_settings |= BIT(1);
+
+	/* Enable 64 transactions if line mode*/
+	if (data->intf_mode == INTF_MODE_WB_LINE)
+		cdp_settings |= BIT(3);
+
+	SDE_REG_WRITE(c, WB_CDP_CTRL, cdp_settings);
 }
 
 static void sde_hw_wb_setup_rotator(struct sde_hw_wb *ctx,
@@ -63,9 +174,13 @@
 		struct sde_hw_wb_cfg *data)
 {
 }
+
 static void sde_hw_wb_traffic_shaper(struct sde_hw_wb *ctx,
 		struct sde_hw_wb_cfg *data)
 {
+	if (ctx->hw_mdp && ctx->hw_mdp->ops.setup_traffic_shaper)
+		ctx->hw_mdp->ops.setup_traffic_shaper(ctx->hw_mdp,
+				&data->ts_cfg);
 }
 
 static void _setup_wb_ops(struct sde_hw_wb_ops *ops,
@@ -92,7 +207,8 @@
 
 struct sde_hw_wb *sde_hw_wb_init(enum sde_wb idx,
 		void __iomem *addr,
-		struct sde_mdss_cfg *m)
+		struct sde_mdss_cfg *m,
+		struct sde_hw_mdp *hw_mdp)
 {
 	struct sde_hw_wb *c;
 	struct sde_wb_cfg *cfg;
@@ -102,7 +218,8 @@
 		return ERR_PTR(-ENOMEM);
 
 	cfg = _wb_offset(idx, m, addr, &c->hw);
-	if (!cfg) {
+	if (IS_ERR(cfg)) {
+		WARN(1, "Unable to find wb idx=%d\n", idx);
 		kfree(c);
 		return ERR_PTR(-EINVAL);
 	}
@@ -111,11 +228,13 @@
 	c->idx = idx;
 	c->caps = cfg;
 	_setup_wb_ops(&c->ops, c->caps->features);
-
-	/*
-	 * Perform any default initialization for the chroma down module
-	 */
+	c->highest_bank_bit = m->mdp[0].highest_bank_bit;
+	c->hw_mdp = hw_mdp;
 
 	return c;
 }
 
+void sde_hw_wb_destroy(struct sde_hw_wb *hw_wb)
+{
+	kfree(hw_wb);
+}
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_wb.h b/drivers/gpu/drm/msm/sde/sde_hw_wb.h
index 093d2fb..e01750b 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_wb.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_wb.h
@@ -15,12 +15,16 @@
 
 #include "sde_hw_catalog.h"
 #include "sde_hw_mdss.h"
+#include "sde_hw_top.h"
 #include "sde_hw_util.h"
 
 struct sde_hw_wb;
 
 struct sde_hw_wb_cfg {
 	struct sde_hw_fmt_layout dest;
+	enum sde_intf_mode intf_mode;
+	struct traffic_shaper_cfg ts_cfg;
+	bool is_secure;
 };
 
 /**
@@ -56,8 +60,9 @@
  * @struct sde_hw_blk_reg_map *hw;
  * @idx
  * @wb_hw_caps
- * @mixer_hw_caps
  * @ops
+ * @highest_bank_bit: GPU highest memory bank bit used
+ * @hw_mdp: MDP top level hardware block
  */
 struct sde_hw_wb {
 	/* base */
@@ -69,17 +74,28 @@
 
 	/* ops */
 	struct sde_hw_wb_ops ops;
+
+	u32 highest_bank_bit;
+
+	struct sde_hw_mdp *hw_mdp;
 };
 
 /**
- * sde_hw_wb_init(): Initializes the wb_path hw driver object.
- * should be called before accessing every mixer.
+ * sde_hw_wb_init(): Initializes and return writeback hw driver object.
  * @idx:  wb_path index for which driver object is required
  * @addr: mapped register io address of MDP
  * @m :   pointer to mdss catalog data
+ * @hw_mdp: pointer to mdp top hw driver object
  */
 struct sde_hw_wb *sde_hw_wb_init(enum sde_wb idx,
 		void __iomem *addr,
-		struct sde_mdss_cfg *m);
+		struct sde_mdss_cfg *m,
+		struct sde_hw_mdp *hw_mdp);
+
+/**
+ * sde_hw_wb_destroy(): Destroy writeback hw driver object.
+ * @hw_wb:  Pointer to writeback hw driver object
+ */
+void sde_hw_wb_destroy(struct sde_hw_wb *hw_wb);
 
 #endif /*_SDE_HW_WB_H */