1ba2d53fbSBenjamin Gaignard /* 2ba2d53fbSBenjamin Gaignard * Copyright (C) STMicroelectronics SA 2014 3ba2d53fbSBenjamin Gaignard * Authors: Benjamin Gaignard <benjamin.gaignard@st.com> 4ba2d53fbSBenjamin Gaignard * Fabien Dessenne <fabien.dessenne@st.com> 5ba2d53fbSBenjamin Gaignard * for STMicroelectronics. 6ba2d53fbSBenjamin Gaignard * License terms: GNU General Public License (GPL), version 2 7ba2d53fbSBenjamin Gaignard */ 8ba2d53fbSBenjamin Gaignard 9ba2d53fbSBenjamin Gaignard #include <linux/clk.h> 10ba2d53fbSBenjamin Gaignard #include <linux/dma-mapping.h> 11ba2d53fbSBenjamin Gaignard 1229d1dc62SVincent Abriou #include <drm/drm_fb_cma_helper.h> 1329d1dc62SVincent Abriou #include <drm/drm_gem_cma_helper.h> 1429d1dc62SVincent Abriou 15d219673dSBenjamin Gaignard #include "sti_compositor.h" 16ba2d53fbSBenjamin Gaignard #include "sti_gdp.h" 179e1f05b2SVincent Abriou #include "sti_plane.h" 18ba2d53fbSBenjamin Gaignard #include "sti_vtg.h" 19ba2d53fbSBenjamin Gaignard 204af6b12aSBenjamin Gaignard #define ALPHASWITCH BIT(6) 21ba2d53fbSBenjamin Gaignard #define ENA_COLOR_FILL BIT(8) 224af6b12aSBenjamin Gaignard #define BIGNOTLITTLE BIT(23) 23ba2d53fbSBenjamin Gaignard #define WAIT_NEXT_VSYNC BIT(31) 24ba2d53fbSBenjamin Gaignard 25ba2d53fbSBenjamin Gaignard /* GDP color formats */ 26ba2d53fbSBenjamin Gaignard #define GDP_RGB565 0x00 27ba2d53fbSBenjamin Gaignard #define GDP_RGB888 0x01 28ba2d53fbSBenjamin Gaignard #define GDP_RGB888_32 0x02 298adb5776SFabien Dessenne #define GDP_XBGR8888 (GDP_RGB888_32 | BIGNOTLITTLE | ALPHASWITCH) 30ba2d53fbSBenjamin Gaignard #define GDP_ARGB8565 0x04 31ba2d53fbSBenjamin Gaignard #define GDP_ARGB8888 0x05 324af6b12aSBenjamin Gaignard #define GDP_ABGR8888 (GDP_ARGB8888 | BIGNOTLITTLE | ALPHASWITCH) 33ba2d53fbSBenjamin Gaignard #define GDP_ARGB1555 0x06 34ba2d53fbSBenjamin Gaignard #define GDP_ARGB4444 0x07 35ba2d53fbSBenjamin Gaignard #define GDP_CLUT8 0x0B 36ba2d53fbSBenjamin Gaignard #define GDP_YCBR888 0x10 37ba2d53fbSBenjamin Gaignard #define GDP_YCBR422R 0x12 38ba2d53fbSBenjamin Gaignard #define GDP_AYCBR8888 0x15 39ba2d53fbSBenjamin Gaignard 40ba2d53fbSBenjamin Gaignard #define GAM_GDP_CTL_OFFSET 0x00 41ba2d53fbSBenjamin Gaignard #define GAM_GDP_AGC_OFFSET 0x04 42ba2d53fbSBenjamin Gaignard #define GAM_GDP_VPO_OFFSET 0x0C 43ba2d53fbSBenjamin Gaignard #define GAM_GDP_VPS_OFFSET 0x10 44ba2d53fbSBenjamin Gaignard #define GAM_GDP_PML_OFFSET 0x14 45ba2d53fbSBenjamin Gaignard #define GAM_GDP_PMP_OFFSET 0x18 46ba2d53fbSBenjamin Gaignard #define GAM_GDP_SIZE_OFFSET 0x1C 47ba2d53fbSBenjamin Gaignard #define GAM_GDP_NVN_OFFSET 0x24 48ba2d53fbSBenjamin Gaignard #define GAM_GDP_KEY1_OFFSET 0x28 49ba2d53fbSBenjamin Gaignard #define GAM_GDP_KEY2_OFFSET 0x2C 50ba2d53fbSBenjamin Gaignard #define GAM_GDP_PPT_OFFSET 0x34 51ba2d53fbSBenjamin Gaignard #define GAM_GDP_CML_OFFSET 0x3C 52ba2d53fbSBenjamin Gaignard #define GAM_GDP_MST_OFFSET 0x68 53ba2d53fbSBenjamin Gaignard 54ba2d53fbSBenjamin Gaignard #define GAM_GDP_ALPHARANGE_255 BIT(5) 55ba2d53fbSBenjamin Gaignard #define GAM_GDP_AGC_FULL_RANGE 0x00808080 56ba2d53fbSBenjamin Gaignard #define GAM_GDP_PPT_IGNORE (BIT(1) | BIT(0)) 57ba2d53fbSBenjamin Gaignard #define GAM_GDP_SIZE_MAX 0x7FF 58ba2d53fbSBenjamin Gaignard 59ba2d53fbSBenjamin Gaignard #define GDP_NODE_NB_BANK 2 60ba2d53fbSBenjamin Gaignard #define GDP_NODE_PER_FIELD 2 61ba2d53fbSBenjamin Gaignard 62ba2d53fbSBenjamin Gaignard struct sti_gdp_node { 63ba2d53fbSBenjamin Gaignard u32 gam_gdp_ctl; 64ba2d53fbSBenjamin Gaignard u32 gam_gdp_agc; 65ba2d53fbSBenjamin Gaignard u32 reserved1; 66ba2d53fbSBenjamin Gaignard u32 gam_gdp_vpo; 67ba2d53fbSBenjamin Gaignard u32 gam_gdp_vps; 68ba2d53fbSBenjamin Gaignard u32 gam_gdp_pml; 69ba2d53fbSBenjamin Gaignard u32 gam_gdp_pmp; 70ba2d53fbSBenjamin Gaignard u32 gam_gdp_size; 71ba2d53fbSBenjamin Gaignard u32 reserved2; 72ba2d53fbSBenjamin Gaignard u32 gam_gdp_nvn; 73ba2d53fbSBenjamin Gaignard u32 gam_gdp_key1; 74ba2d53fbSBenjamin Gaignard u32 gam_gdp_key2; 75ba2d53fbSBenjamin Gaignard u32 reserved3; 76ba2d53fbSBenjamin Gaignard u32 gam_gdp_ppt; 77ba2d53fbSBenjamin Gaignard u32 reserved4; 78ba2d53fbSBenjamin Gaignard u32 gam_gdp_cml; 79ba2d53fbSBenjamin Gaignard }; 80ba2d53fbSBenjamin Gaignard 81ba2d53fbSBenjamin Gaignard struct sti_gdp_node_list { 82ba2d53fbSBenjamin Gaignard struct sti_gdp_node *top_field; 83a51fe84dSBenjamin Gaignard dma_addr_t top_field_paddr; 84ba2d53fbSBenjamin Gaignard struct sti_gdp_node *btm_field; 85a51fe84dSBenjamin Gaignard dma_addr_t btm_field_paddr; 86ba2d53fbSBenjamin Gaignard }; 87ba2d53fbSBenjamin Gaignard 88ba2d53fbSBenjamin Gaignard /** 89ba2d53fbSBenjamin Gaignard * STI GDP structure 90ba2d53fbSBenjamin Gaignard * 91871bcdfeSVincent Abriou * @sti_plane: sti_plane structure 92871bcdfeSVincent Abriou * @dev: driver device 93871bcdfeSVincent Abriou * @regs: gdp registers 94ba2d53fbSBenjamin Gaignard * @clk_pix: pixel clock for the current gdp 955e03abc5SBenjamin Gaignard * @clk_main_parent: gdp parent clock if main path used 965e03abc5SBenjamin Gaignard * @clk_aux_parent: gdp parent clock if aux path used 97ba2d53fbSBenjamin Gaignard * @vtg_field_nb: callback for VTG FIELD (top or bottom) notification 98ba2d53fbSBenjamin Gaignard * @is_curr_top: true if the current node processed is the top field 99ba2d53fbSBenjamin Gaignard * @node_list: array of node list 100ba2d53fbSBenjamin Gaignard */ 101ba2d53fbSBenjamin Gaignard struct sti_gdp { 102871bcdfeSVincent Abriou struct sti_plane plane; 103871bcdfeSVincent Abriou struct device *dev; 104871bcdfeSVincent Abriou void __iomem *regs; 105ba2d53fbSBenjamin Gaignard struct clk *clk_pix; 1065e03abc5SBenjamin Gaignard struct clk *clk_main_parent; 1075e03abc5SBenjamin Gaignard struct clk *clk_aux_parent; 108ba2d53fbSBenjamin Gaignard struct notifier_block vtg_field_nb; 109ba2d53fbSBenjamin Gaignard bool is_curr_top; 110ba2d53fbSBenjamin Gaignard struct sti_gdp_node_list node_list[GDP_NODE_NB_BANK]; 111ba2d53fbSBenjamin Gaignard }; 112ba2d53fbSBenjamin Gaignard 113871bcdfeSVincent Abriou #define to_sti_gdp(x) container_of(x, struct sti_gdp, plane) 114ba2d53fbSBenjamin Gaignard 115ba2d53fbSBenjamin Gaignard static const uint32_t gdp_supported_formats[] = { 116ba2d53fbSBenjamin Gaignard DRM_FORMAT_XRGB8888, 1178adb5776SFabien Dessenne DRM_FORMAT_XBGR8888, 118ba2d53fbSBenjamin Gaignard DRM_FORMAT_ARGB8888, 1194af6b12aSBenjamin Gaignard DRM_FORMAT_ABGR8888, 120ba2d53fbSBenjamin Gaignard DRM_FORMAT_ARGB4444, 121ba2d53fbSBenjamin Gaignard DRM_FORMAT_ARGB1555, 122ba2d53fbSBenjamin Gaignard DRM_FORMAT_RGB565, 123ba2d53fbSBenjamin Gaignard DRM_FORMAT_RGB888, 124ba2d53fbSBenjamin Gaignard DRM_FORMAT_AYUV, 125ba2d53fbSBenjamin Gaignard DRM_FORMAT_YUV444, 126ba2d53fbSBenjamin Gaignard DRM_FORMAT_VYUY, 127ba2d53fbSBenjamin Gaignard DRM_FORMAT_C8, 128ba2d53fbSBenjamin Gaignard }; 129ba2d53fbSBenjamin Gaignard 130ba2d53fbSBenjamin Gaignard static int sti_gdp_fourcc2format(int fourcc) 131ba2d53fbSBenjamin Gaignard { 132ba2d53fbSBenjamin Gaignard switch (fourcc) { 133ba2d53fbSBenjamin Gaignard case DRM_FORMAT_XRGB8888: 134ba2d53fbSBenjamin Gaignard return GDP_RGB888_32; 1358adb5776SFabien Dessenne case DRM_FORMAT_XBGR8888: 1368adb5776SFabien Dessenne return GDP_XBGR8888; 137ba2d53fbSBenjamin Gaignard case DRM_FORMAT_ARGB8888: 138ba2d53fbSBenjamin Gaignard return GDP_ARGB8888; 1394af6b12aSBenjamin Gaignard case DRM_FORMAT_ABGR8888: 1404af6b12aSBenjamin Gaignard return GDP_ABGR8888; 141ba2d53fbSBenjamin Gaignard case DRM_FORMAT_ARGB4444: 142ba2d53fbSBenjamin Gaignard return GDP_ARGB4444; 143ba2d53fbSBenjamin Gaignard case DRM_FORMAT_ARGB1555: 144ba2d53fbSBenjamin Gaignard return GDP_ARGB1555; 145ba2d53fbSBenjamin Gaignard case DRM_FORMAT_RGB565: 146ba2d53fbSBenjamin Gaignard return GDP_RGB565; 147ba2d53fbSBenjamin Gaignard case DRM_FORMAT_RGB888: 148ba2d53fbSBenjamin Gaignard return GDP_RGB888; 149ba2d53fbSBenjamin Gaignard case DRM_FORMAT_AYUV: 150ba2d53fbSBenjamin Gaignard return GDP_AYCBR8888; 151ba2d53fbSBenjamin Gaignard case DRM_FORMAT_YUV444: 152ba2d53fbSBenjamin Gaignard return GDP_YCBR888; 153ba2d53fbSBenjamin Gaignard case DRM_FORMAT_VYUY: 154ba2d53fbSBenjamin Gaignard return GDP_YCBR422R; 155ba2d53fbSBenjamin Gaignard case DRM_FORMAT_C8: 156ba2d53fbSBenjamin Gaignard return GDP_CLUT8; 157ba2d53fbSBenjamin Gaignard } 158ba2d53fbSBenjamin Gaignard return -1; 159ba2d53fbSBenjamin Gaignard } 160ba2d53fbSBenjamin Gaignard 161ba2d53fbSBenjamin Gaignard static int sti_gdp_get_alpharange(int format) 162ba2d53fbSBenjamin Gaignard { 163ba2d53fbSBenjamin Gaignard switch (format) { 164ba2d53fbSBenjamin Gaignard case GDP_ARGB8565: 165ba2d53fbSBenjamin Gaignard case GDP_ARGB8888: 166ba2d53fbSBenjamin Gaignard case GDP_AYCBR8888: 1674af6b12aSBenjamin Gaignard case GDP_ABGR8888: 168ba2d53fbSBenjamin Gaignard return GAM_GDP_ALPHARANGE_255; 169ba2d53fbSBenjamin Gaignard } 170ba2d53fbSBenjamin Gaignard return 0; 171ba2d53fbSBenjamin Gaignard } 172ba2d53fbSBenjamin Gaignard 173ba2d53fbSBenjamin Gaignard /** 174ba2d53fbSBenjamin Gaignard * sti_gdp_get_free_nodes 17529d1dc62SVincent Abriou * @gdp: gdp pointer 176ba2d53fbSBenjamin Gaignard * 177ba2d53fbSBenjamin Gaignard * Look for a GDP node list that is not currently read by the HW. 178ba2d53fbSBenjamin Gaignard * 179ba2d53fbSBenjamin Gaignard * RETURNS: 180ba2d53fbSBenjamin Gaignard * Pointer to the free GDP node list 181ba2d53fbSBenjamin Gaignard */ 18229d1dc62SVincent Abriou static struct sti_gdp_node_list *sti_gdp_get_free_nodes(struct sti_gdp *gdp) 183ba2d53fbSBenjamin Gaignard { 184ba2d53fbSBenjamin Gaignard int hw_nvn; 185ba2d53fbSBenjamin Gaignard unsigned int i; 186ba2d53fbSBenjamin Gaignard 187871bcdfeSVincent Abriou hw_nvn = readl(gdp->regs + GAM_GDP_NVN_OFFSET); 188ba2d53fbSBenjamin Gaignard if (!hw_nvn) 189ba2d53fbSBenjamin Gaignard goto end; 190ba2d53fbSBenjamin Gaignard 191ba2d53fbSBenjamin Gaignard for (i = 0; i < GDP_NODE_NB_BANK; i++) 192a51fe84dSBenjamin Gaignard if ((hw_nvn != gdp->node_list[i].btm_field_paddr) && 193a51fe84dSBenjamin Gaignard (hw_nvn != gdp->node_list[i].top_field_paddr)) 194ba2d53fbSBenjamin Gaignard return &gdp->node_list[i]; 195ba2d53fbSBenjamin Gaignard 196d219673dSBenjamin Gaignard /* in hazardious cases restart with the first node */ 197d219673dSBenjamin Gaignard DRM_ERROR("inconsistent NVN for %s: 0x%08X\n", 19829d1dc62SVincent Abriou sti_plane_to_str(&gdp->plane), hw_nvn); 199d219673dSBenjamin Gaignard 200ba2d53fbSBenjamin Gaignard end: 201ba2d53fbSBenjamin Gaignard return &gdp->node_list[0]; 202ba2d53fbSBenjamin Gaignard } 203ba2d53fbSBenjamin Gaignard 204ba2d53fbSBenjamin Gaignard /** 205ba2d53fbSBenjamin Gaignard * sti_gdp_get_current_nodes 20629d1dc62SVincent Abriou * @gdp: gdp pointer 207ba2d53fbSBenjamin Gaignard * 208ba2d53fbSBenjamin Gaignard * Look for GDP nodes that are currently read by the HW. 209ba2d53fbSBenjamin Gaignard * 210ba2d53fbSBenjamin Gaignard * RETURNS: 211ba2d53fbSBenjamin Gaignard * Pointer to the current GDP node list 212ba2d53fbSBenjamin Gaignard */ 213ba2d53fbSBenjamin Gaignard static 21429d1dc62SVincent Abriou struct sti_gdp_node_list *sti_gdp_get_current_nodes(struct sti_gdp *gdp) 215ba2d53fbSBenjamin Gaignard { 216ba2d53fbSBenjamin Gaignard int hw_nvn; 217ba2d53fbSBenjamin Gaignard unsigned int i; 218ba2d53fbSBenjamin Gaignard 219871bcdfeSVincent Abriou hw_nvn = readl(gdp->regs + GAM_GDP_NVN_OFFSET); 220ba2d53fbSBenjamin Gaignard if (!hw_nvn) 221ba2d53fbSBenjamin Gaignard goto end; 222ba2d53fbSBenjamin Gaignard 223ba2d53fbSBenjamin Gaignard for (i = 0; i < GDP_NODE_NB_BANK; i++) 224a51fe84dSBenjamin Gaignard if ((hw_nvn == gdp->node_list[i].btm_field_paddr) || 225a51fe84dSBenjamin Gaignard (hw_nvn == gdp->node_list[i].top_field_paddr)) 226ba2d53fbSBenjamin Gaignard return &gdp->node_list[i]; 227ba2d53fbSBenjamin Gaignard 228ba2d53fbSBenjamin Gaignard end: 229d219673dSBenjamin Gaignard DRM_DEBUG_DRIVER("Warning, NVN 0x%08X for %s does not match any node\n", 23029d1dc62SVincent Abriou hw_nvn, sti_plane_to_str(&gdp->plane)); 231d219673dSBenjamin Gaignard 232ba2d53fbSBenjamin Gaignard return NULL; 233ba2d53fbSBenjamin Gaignard } 234ba2d53fbSBenjamin Gaignard 235ba2d53fbSBenjamin Gaignard /** 236871bcdfeSVincent Abriou * sti_gdp_disable 23729d1dc62SVincent Abriou * @gdp: gdp pointer 238ba2d53fbSBenjamin Gaignard * 239ba2d53fbSBenjamin Gaignard * Disable a GDP. 240ba2d53fbSBenjamin Gaignard */ 24129d1dc62SVincent Abriou static void sti_gdp_disable(struct sti_gdp *gdp) 242ba2d53fbSBenjamin Gaignard { 24329d1dc62SVincent Abriou struct drm_plane *drm_plane = &gdp->plane.drm_plane; 24429d1dc62SVincent Abriou struct sti_mixer *mixer = to_sti_mixer(drm_plane->crtc); 245871bcdfeSVincent Abriou struct sti_compositor *compo = dev_get_drvdata(gdp->dev); 24629d1dc62SVincent Abriou unsigned int i; 247d219673dSBenjamin Gaignard 24829d1dc62SVincent Abriou DRM_DEBUG_DRIVER("%s\n", sti_plane_to_str(&gdp->plane)); 249ba2d53fbSBenjamin Gaignard 250ba2d53fbSBenjamin Gaignard /* Set the nodes as 'to be ignored on mixer' */ 251ba2d53fbSBenjamin Gaignard for (i = 0; i < GDP_NODE_NB_BANK; i++) { 252ba2d53fbSBenjamin Gaignard gdp->node_list[i].top_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE; 253ba2d53fbSBenjamin Gaignard gdp->node_list[i].btm_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE; 254ba2d53fbSBenjamin Gaignard } 255ba2d53fbSBenjamin Gaignard 25629d1dc62SVincent Abriou if (sti_vtg_unregister_client(mixer->id == STI_MIXER_MAIN ? 257d219673dSBenjamin Gaignard compo->vtg_main : compo->vtg_aux, &gdp->vtg_field_nb)) 258d219673dSBenjamin Gaignard DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n"); 259d219673dSBenjamin Gaignard 260ba2d53fbSBenjamin Gaignard if (gdp->clk_pix) 261ba2d53fbSBenjamin Gaignard clk_disable_unprepare(gdp->clk_pix); 262ba2d53fbSBenjamin Gaignard 26329d1dc62SVincent Abriou gdp->plane.status = STI_PLANE_DISABLED; 264ba2d53fbSBenjamin Gaignard } 265ba2d53fbSBenjamin Gaignard 266ba2d53fbSBenjamin Gaignard /** 267ba2d53fbSBenjamin Gaignard * sti_gdp_field_cb 268ba2d53fbSBenjamin Gaignard * @nb: notifier block 269ba2d53fbSBenjamin Gaignard * @event: event message 270ba2d53fbSBenjamin Gaignard * @data: private data 271ba2d53fbSBenjamin Gaignard * 272ba2d53fbSBenjamin Gaignard * Handle VTG top field and bottom field event. 273ba2d53fbSBenjamin Gaignard * 274ba2d53fbSBenjamin Gaignard * RETURNS: 275ba2d53fbSBenjamin Gaignard * 0 on success. 276ba2d53fbSBenjamin Gaignard */ 277ba2d53fbSBenjamin Gaignard int sti_gdp_field_cb(struct notifier_block *nb, 278ba2d53fbSBenjamin Gaignard unsigned long event, void *data) 279ba2d53fbSBenjamin Gaignard { 280ba2d53fbSBenjamin Gaignard struct sti_gdp *gdp = container_of(nb, struct sti_gdp, vtg_field_nb); 281ba2d53fbSBenjamin Gaignard 28229d1dc62SVincent Abriou if (gdp->plane.status == STI_PLANE_FLUSHING) { 28329d1dc62SVincent Abriou /* disable need to be synchronize on vsync event */ 28429d1dc62SVincent Abriou DRM_DEBUG_DRIVER("Vsync event received => disable %s\n", 28529d1dc62SVincent Abriou sti_plane_to_str(&gdp->plane)); 28629d1dc62SVincent Abriou 28729d1dc62SVincent Abriou sti_gdp_disable(gdp); 28829d1dc62SVincent Abriou } 28929d1dc62SVincent Abriou 290ba2d53fbSBenjamin Gaignard switch (event) { 291ba2d53fbSBenjamin Gaignard case VTG_TOP_FIELD_EVENT: 292ba2d53fbSBenjamin Gaignard gdp->is_curr_top = true; 293ba2d53fbSBenjamin Gaignard break; 294ba2d53fbSBenjamin Gaignard case VTG_BOTTOM_FIELD_EVENT: 295ba2d53fbSBenjamin Gaignard gdp->is_curr_top = false; 296ba2d53fbSBenjamin Gaignard break; 297ba2d53fbSBenjamin Gaignard default: 298ba2d53fbSBenjamin Gaignard DRM_ERROR("unsupported event: %lu\n", event); 299ba2d53fbSBenjamin Gaignard break; 300ba2d53fbSBenjamin Gaignard } 301ba2d53fbSBenjamin Gaignard 302ba2d53fbSBenjamin Gaignard return 0; 303ba2d53fbSBenjamin Gaignard } 304ba2d53fbSBenjamin Gaignard 305871bcdfeSVincent Abriou static void sti_gdp_init(struct sti_gdp *gdp) 306ba2d53fbSBenjamin Gaignard { 307871bcdfeSVincent Abriou struct device_node *np = gdp->dev->of_node; 308a51fe84dSBenjamin Gaignard dma_addr_t dma_addr; 309ba2d53fbSBenjamin Gaignard void *base; 310ba2d53fbSBenjamin Gaignard unsigned int i, size; 311ba2d53fbSBenjamin Gaignard 312ba2d53fbSBenjamin Gaignard /* Allocate all the nodes within a single memory page */ 313ba2d53fbSBenjamin Gaignard size = sizeof(struct sti_gdp_node) * 314ba2d53fbSBenjamin Gaignard GDP_NODE_PER_FIELD * GDP_NODE_NB_BANK; 315f6e45661SLuis R. Rodriguez base = dma_alloc_wc(gdp->dev, size, &dma_addr, GFP_KERNEL | GFP_DMA); 316a51fe84dSBenjamin Gaignard 317ba2d53fbSBenjamin Gaignard if (!base) { 318ba2d53fbSBenjamin Gaignard DRM_ERROR("Failed to allocate memory for GDP node\n"); 319ba2d53fbSBenjamin Gaignard return; 320ba2d53fbSBenjamin Gaignard } 321ba2d53fbSBenjamin Gaignard memset(base, 0, size); 322ba2d53fbSBenjamin Gaignard 323ba2d53fbSBenjamin Gaignard for (i = 0; i < GDP_NODE_NB_BANK; i++) { 324a51fe84dSBenjamin Gaignard if (dma_addr & 0xF) { 325ba2d53fbSBenjamin Gaignard DRM_ERROR("Mem alignment failed\n"); 326ba2d53fbSBenjamin Gaignard return; 327ba2d53fbSBenjamin Gaignard } 328ba2d53fbSBenjamin Gaignard gdp->node_list[i].top_field = base; 329a51fe84dSBenjamin Gaignard gdp->node_list[i].top_field_paddr = dma_addr; 330a51fe84dSBenjamin Gaignard 331ba2d53fbSBenjamin Gaignard DRM_DEBUG_DRIVER("node[%d].top_field=%p\n", i, base); 332ba2d53fbSBenjamin Gaignard base += sizeof(struct sti_gdp_node); 333a51fe84dSBenjamin Gaignard dma_addr += sizeof(struct sti_gdp_node); 334ba2d53fbSBenjamin Gaignard 335a51fe84dSBenjamin Gaignard if (dma_addr & 0xF) { 336ba2d53fbSBenjamin Gaignard DRM_ERROR("Mem alignment failed\n"); 337ba2d53fbSBenjamin Gaignard return; 338ba2d53fbSBenjamin Gaignard } 339ba2d53fbSBenjamin Gaignard gdp->node_list[i].btm_field = base; 340a51fe84dSBenjamin Gaignard gdp->node_list[i].btm_field_paddr = dma_addr; 341ba2d53fbSBenjamin Gaignard DRM_DEBUG_DRIVER("node[%d].btm_field=%p\n", i, base); 342ba2d53fbSBenjamin Gaignard base += sizeof(struct sti_gdp_node); 343a51fe84dSBenjamin Gaignard dma_addr += sizeof(struct sti_gdp_node); 344ba2d53fbSBenjamin Gaignard } 345ba2d53fbSBenjamin Gaignard 346ba2d53fbSBenjamin Gaignard if (of_device_is_compatible(np, "st,stih407-compositor")) { 347ba2d53fbSBenjamin Gaignard /* GDP of STiH407 chip have its own pixel clock */ 348ba2d53fbSBenjamin Gaignard char *clk_name; 349ba2d53fbSBenjamin Gaignard 350871bcdfeSVincent Abriou switch (gdp->plane.desc) { 351ba2d53fbSBenjamin Gaignard case STI_GDP_0: 352ba2d53fbSBenjamin Gaignard clk_name = "pix_gdp1"; 353ba2d53fbSBenjamin Gaignard break; 354ba2d53fbSBenjamin Gaignard case STI_GDP_1: 355ba2d53fbSBenjamin Gaignard clk_name = "pix_gdp2"; 356ba2d53fbSBenjamin Gaignard break; 357ba2d53fbSBenjamin Gaignard case STI_GDP_2: 358ba2d53fbSBenjamin Gaignard clk_name = "pix_gdp3"; 359ba2d53fbSBenjamin Gaignard break; 360ba2d53fbSBenjamin Gaignard case STI_GDP_3: 361ba2d53fbSBenjamin Gaignard clk_name = "pix_gdp4"; 362ba2d53fbSBenjamin Gaignard break; 363ba2d53fbSBenjamin Gaignard default: 364ba2d53fbSBenjamin Gaignard DRM_ERROR("GDP id not recognized\n"); 365ba2d53fbSBenjamin Gaignard return; 366ba2d53fbSBenjamin Gaignard } 367ba2d53fbSBenjamin Gaignard 368871bcdfeSVincent Abriou gdp->clk_pix = devm_clk_get(gdp->dev, clk_name); 369ba2d53fbSBenjamin Gaignard if (IS_ERR(gdp->clk_pix)) 370ba2d53fbSBenjamin Gaignard DRM_ERROR("Cannot get %s clock\n", clk_name); 3715e03abc5SBenjamin Gaignard 372871bcdfeSVincent Abriou gdp->clk_main_parent = devm_clk_get(gdp->dev, "main_parent"); 3735e03abc5SBenjamin Gaignard if (IS_ERR(gdp->clk_main_parent)) 3745e03abc5SBenjamin Gaignard DRM_ERROR("Cannot get main_parent clock\n"); 3755e03abc5SBenjamin Gaignard 376871bcdfeSVincent Abriou gdp->clk_aux_parent = devm_clk_get(gdp->dev, "aux_parent"); 3775e03abc5SBenjamin Gaignard if (IS_ERR(gdp->clk_aux_parent)) 3785e03abc5SBenjamin Gaignard DRM_ERROR("Cannot get aux_parent clock\n"); 379ba2d53fbSBenjamin Gaignard } 380ba2d53fbSBenjamin Gaignard } 381ba2d53fbSBenjamin Gaignard 38229d1dc62SVincent Abriou static void sti_gdp_atomic_update(struct drm_plane *drm_plane, 38329d1dc62SVincent Abriou struct drm_plane_state *oldstate) 38429d1dc62SVincent Abriou { 38529d1dc62SVincent Abriou struct drm_plane_state *state = drm_plane->state; 38629d1dc62SVincent Abriou struct sti_plane *plane = to_sti_plane(drm_plane); 38729d1dc62SVincent Abriou struct sti_gdp *gdp = to_sti_gdp(plane); 38829d1dc62SVincent Abriou struct drm_crtc *crtc = state->crtc; 38929d1dc62SVincent Abriou struct sti_compositor *compo = dev_get_drvdata(gdp->dev); 39029d1dc62SVincent Abriou struct drm_framebuffer *fb = state->fb; 39129d1dc62SVincent Abriou bool first_prepare = plane->status == STI_PLANE_DISABLED ? true : false; 39229d1dc62SVincent Abriou struct sti_mixer *mixer; 39329d1dc62SVincent Abriou struct drm_display_mode *mode; 39429d1dc62SVincent Abriou int dst_x, dst_y, dst_w, dst_h; 39529d1dc62SVincent Abriou int src_x, src_y, src_w, src_h; 39629d1dc62SVincent Abriou struct drm_gem_cma_object *cma_obj; 39729d1dc62SVincent Abriou struct sti_gdp_node_list *list; 39829d1dc62SVincent Abriou struct sti_gdp_node_list *curr_list; 39929d1dc62SVincent Abriou struct sti_gdp_node *top_field, *btm_field; 40029d1dc62SVincent Abriou u32 dma_updated_top; 40129d1dc62SVincent Abriou u32 dma_updated_btm; 40229d1dc62SVincent Abriou int format; 40329d1dc62SVincent Abriou unsigned int depth, bpp; 40429d1dc62SVincent Abriou u32 ydo, xdo, yds, xds; 40529d1dc62SVincent Abriou int res; 40629d1dc62SVincent Abriou 40729d1dc62SVincent Abriou /* Manage the case where crtc is null (disabled) */ 40829d1dc62SVincent Abriou if (!crtc) 40929d1dc62SVincent Abriou return; 41029d1dc62SVincent Abriou 41129d1dc62SVincent Abriou mixer = to_sti_mixer(crtc); 41229d1dc62SVincent Abriou mode = &crtc->mode; 41329d1dc62SVincent Abriou dst_x = state->crtc_x; 41429d1dc62SVincent Abriou dst_y = state->crtc_y; 41529d1dc62SVincent Abriou dst_w = clamp_val(state->crtc_w, 0, mode->crtc_hdisplay - dst_x); 41629d1dc62SVincent Abriou dst_h = clamp_val(state->crtc_h, 0, mode->crtc_vdisplay - dst_y); 41729d1dc62SVincent Abriou /* src_x are in 16.16 format */ 41829d1dc62SVincent Abriou src_x = state->src_x >> 16; 41929d1dc62SVincent Abriou src_y = state->src_y >> 16; 42029d1dc62SVincent Abriou src_w = state->src_w >> 16; 42129d1dc62SVincent Abriou src_h = state->src_h >> 16; 42229d1dc62SVincent Abriou 42329d1dc62SVincent Abriou DRM_DEBUG_KMS("CRTC:%d (%s) drm plane:%d (%s)\n", 42429d1dc62SVincent Abriou crtc->base.id, sti_mixer_to_str(mixer), 42529d1dc62SVincent Abriou drm_plane->base.id, sti_plane_to_str(plane)); 42629d1dc62SVincent Abriou DRM_DEBUG_KMS("%s dst=(%dx%d)@(%d,%d) - src=(%dx%d)@(%d,%d)\n", 42729d1dc62SVincent Abriou sti_plane_to_str(plane), 42829d1dc62SVincent Abriou dst_w, dst_h, dst_x, dst_y, 42929d1dc62SVincent Abriou src_w, src_h, src_x, src_y); 43029d1dc62SVincent Abriou 43129d1dc62SVincent Abriou list = sti_gdp_get_free_nodes(gdp); 43229d1dc62SVincent Abriou top_field = list->top_field; 43329d1dc62SVincent Abriou btm_field = list->btm_field; 43429d1dc62SVincent Abriou 43529d1dc62SVincent Abriou dev_dbg(gdp->dev, "%s %s top_node:0x%p btm_node:0x%p\n", __func__, 43629d1dc62SVincent Abriou sti_plane_to_str(plane), top_field, btm_field); 43729d1dc62SVincent Abriou 43829d1dc62SVincent Abriou /* build the top field */ 43929d1dc62SVincent Abriou top_field->gam_gdp_agc = GAM_GDP_AGC_FULL_RANGE; 44029d1dc62SVincent Abriou top_field->gam_gdp_ctl = WAIT_NEXT_VSYNC; 44129d1dc62SVincent Abriou format = sti_gdp_fourcc2format(fb->pixel_format); 44229d1dc62SVincent Abriou if (format == -1) { 44329d1dc62SVincent Abriou DRM_ERROR("Format not supported by GDP %.4s\n", 44429d1dc62SVincent Abriou (char *)&fb->pixel_format); 44529d1dc62SVincent Abriou return; 44629d1dc62SVincent Abriou } 44729d1dc62SVincent Abriou top_field->gam_gdp_ctl |= format; 44829d1dc62SVincent Abriou top_field->gam_gdp_ctl |= sti_gdp_get_alpharange(format); 44929d1dc62SVincent Abriou top_field->gam_gdp_ppt &= ~GAM_GDP_PPT_IGNORE; 45029d1dc62SVincent Abriou 45129d1dc62SVincent Abriou cma_obj = drm_fb_cma_get_gem_obj(fb, 0); 45229d1dc62SVincent Abriou if (!cma_obj) { 45329d1dc62SVincent Abriou DRM_ERROR("Can't get CMA GEM object for fb\n"); 45429d1dc62SVincent Abriou return; 45529d1dc62SVincent Abriou } 45629d1dc62SVincent Abriou 45729d1dc62SVincent Abriou DRM_DEBUG_DRIVER("drm FB:%d format:%.4s phys@:0x%lx\n", fb->base.id, 45829d1dc62SVincent Abriou (char *)&fb->pixel_format, 45929d1dc62SVincent Abriou (unsigned long)cma_obj->paddr); 46029d1dc62SVincent Abriou 46129d1dc62SVincent Abriou /* pixel memory location */ 46229d1dc62SVincent Abriou drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp); 46329d1dc62SVincent Abriou top_field->gam_gdp_pml = (u32)cma_obj->paddr + fb->offsets[0]; 46429d1dc62SVincent Abriou top_field->gam_gdp_pml += src_x * (bpp >> 3); 46529d1dc62SVincent Abriou top_field->gam_gdp_pml += src_y * fb->pitches[0]; 46629d1dc62SVincent Abriou 46729d1dc62SVincent Abriou /* input parameters */ 46829d1dc62SVincent Abriou top_field->gam_gdp_pmp = fb->pitches[0]; 46929d1dc62SVincent Abriou top_field->gam_gdp_size = clamp_val(src_h, 0, GAM_GDP_SIZE_MAX) << 16 | 47029d1dc62SVincent Abriou clamp_val(src_w, 0, GAM_GDP_SIZE_MAX); 47129d1dc62SVincent Abriou 47229d1dc62SVincent Abriou /* output parameters */ 47329d1dc62SVincent Abriou ydo = sti_vtg_get_line_number(*mode, dst_y); 47429d1dc62SVincent Abriou yds = sti_vtg_get_line_number(*mode, dst_y + dst_h - 1); 47529d1dc62SVincent Abriou xdo = sti_vtg_get_pixel_number(*mode, dst_x); 47629d1dc62SVincent Abriou xds = sti_vtg_get_pixel_number(*mode, dst_x + dst_w - 1); 47729d1dc62SVincent Abriou top_field->gam_gdp_vpo = (ydo << 16) | xdo; 47829d1dc62SVincent Abriou top_field->gam_gdp_vps = (yds << 16) | xds; 47929d1dc62SVincent Abriou 48029d1dc62SVincent Abriou /* Same content and chained together */ 48129d1dc62SVincent Abriou memcpy(btm_field, top_field, sizeof(*btm_field)); 48229d1dc62SVincent Abriou top_field->gam_gdp_nvn = list->btm_field_paddr; 48329d1dc62SVincent Abriou btm_field->gam_gdp_nvn = list->top_field_paddr; 48429d1dc62SVincent Abriou 48529d1dc62SVincent Abriou /* Interlaced mode */ 48629d1dc62SVincent Abriou if (mode->flags & DRM_MODE_FLAG_INTERLACE) 48729d1dc62SVincent Abriou btm_field->gam_gdp_pml = top_field->gam_gdp_pml + 48829d1dc62SVincent Abriou fb->pitches[0]; 48929d1dc62SVincent Abriou 49029d1dc62SVincent Abriou if (first_prepare) { 49129d1dc62SVincent Abriou /* Register gdp callback */ 49229d1dc62SVincent Abriou if (sti_vtg_register_client(mixer->id == STI_MIXER_MAIN ? 49329d1dc62SVincent Abriou compo->vtg_main : compo->vtg_aux, 4942388693eSThierry Reding &gdp->vtg_field_nb, crtc)) { 49529d1dc62SVincent Abriou DRM_ERROR("Cannot register VTG notifier\n"); 49629d1dc62SVincent Abriou return; 49729d1dc62SVincent Abriou } 49829d1dc62SVincent Abriou 49929d1dc62SVincent Abriou /* Set and enable gdp clock */ 50029d1dc62SVincent Abriou if (gdp->clk_pix) { 50129d1dc62SVincent Abriou struct clk *clkp; 50229d1dc62SVincent Abriou int rate = mode->clock * 1000; 50329d1dc62SVincent Abriou 50429d1dc62SVincent Abriou /* According to the mixer used, the gdp pixel clock 50529d1dc62SVincent Abriou * should have a different parent clock. */ 50629d1dc62SVincent Abriou if (mixer->id == STI_MIXER_MAIN) 50729d1dc62SVincent Abriou clkp = gdp->clk_main_parent; 50829d1dc62SVincent Abriou else 50929d1dc62SVincent Abriou clkp = gdp->clk_aux_parent; 51029d1dc62SVincent Abriou 51129d1dc62SVincent Abriou if (clkp) 51229d1dc62SVincent Abriou clk_set_parent(gdp->clk_pix, clkp); 51329d1dc62SVincent Abriou 51429d1dc62SVincent Abriou res = clk_set_rate(gdp->clk_pix, rate); 51529d1dc62SVincent Abriou if (res < 0) { 51629d1dc62SVincent Abriou DRM_ERROR("Cannot set rate (%dHz) for gdp\n", 51729d1dc62SVincent Abriou rate); 51829d1dc62SVincent Abriou return; 51929d1dc62SVincent Abriou } 52029d1dc62SVincent Abriou 52129d1dc62SVincent Abriou if (clk_prepare_enable(gdp->clk_pix)) { 52229d1dc62SVincent Abriou DRM_ERROR("Failed to prepare/enable gdp\n"); 52329d1dc62SVincent Abriou return; 52429d1dc62SVincent Abriou } 52529d1dc62SVincent Abriou } 52629d1dc62SVincent Abriou } 52729d1dc62SVincent Abriou 52829d1dc62SVincent Abriou /* Update the NVN field of the 'right' field of the current GDP node 52929d1dc62SVincent Abriou * (being used by the HW) with the address of the updated ('free') top 53029d1dc62SVincent Abriou * field GDP node. 53129d1dc62SVincent Abriou * - In interlaced mode the 'right' field is the bottom field as we 53229d1dc62SVincent Abriou * update frames starting from their top field 53329d1dc62SVincent Abriou * - In progressive mode, we update both bottom and top fields which 53429d1dc62SVincent Abriou * are equal nodes. 53529d1dc62SVincent Abriou * At the next VSYNC, the updated node list will be used by the HW. 53629d1dc62SVincent Abriou */ 53729d1dc62SVincent Abriou curr_list = sti_gdp_get_current_nodes(gdp); 53829d1dc62SVincent Abriou dma_updated_top = list->top_field_paddr; 53929d1dc62SVincent Abriou dma_updated_btm = list->btm_field_paddr; 54029d1dc62SVincent Abriou 54129d1dc62SVincent Abriou dev_dbg(gdp->dev, "Current NVN:0x%X\n", 54229d1dc62SVincent Abriou readl(gdp->regs + GAM_GDP_NVN_OFFSET)); 54329d1dc62SVincent Abriou dev_dbg(gdp->dev, "Posted buff: %lx current buff: %x\n", 54429d1dc62SVincent Abriou (unsigned long)cma_obj->paddr, 54529d1dc62SVincent Abriou readl(gdp->regs + GAM_GDP_PML_OFFSET)); 54629d1dc62SVincent Abriou 54729d1dc62SVincent Abriou if (!curr_list) { 54829d1dc62SVincent Abriou /* First update or invalid node should directly write in the 54929d1dc62SVincent Abriou * hw register */ 55029d1dc62SVincent Abriou DRM_DEBUG_DRIVER("%s first update (or invalid node)", 55129d1dc62SVincent Abriou sti_plane_to_str(plane)); 55229d1dc62SVincent Abriou 55329d1dc62SVincent Abriou writel(gdp->is_curr_top ? 55429d1dc62SVincent Abriou dma_updated_btm : dma_updated_top, 55529d1dc62SVincent Abriou gdp->regs + GAM_GDP_NVN_OFFSET); 55629d1dc62SVincent Abriou goto end; 55729d1dc62SVincent Abriou } 55829d1dc62SVincent Abriou 55929d1dc62SVincent Abriou if (mode->flags & DRM_MODE_FLAG_INTERLACE) { 56029d1dc62SVincent Abriou if (gdp->is_curr_top) { 56129d1dc62SVincent Abriou /* Do not update in the middle of the frame, but 56229d1dc62SVincent Abriou * postpone the update after the bottom field has 56329d1dc62SVincent Abriou * been displayed */ 56429d1dc62SVincent Abriou curr_list->btm_field->gam_gdp_nvn = dma_updated_top; 56529d1dc62SVincent Abriou } else { 56629d1dc62SVincent Abriou /* Direct update to avoid one frame delay */ 56729d1dc62SVincent Abriou writel(dma_updated_top, 56829d1dc62SVincent Abriou gdp->regs + GAM_GDP_NVN_OFFSET); 56929d1dc62SVincent Abriou } 57029d1dc62SVincent Abriou } else { 57129d1dc62SVincent Abriou /* Direct update for progressive to avoid one frame delay */ 57229d1dc62SVincent Abriou writel(dma_updated_top, gdp->regs + GAM_GDP_NVN_OFFSET); 57329d1dc62SVincent Abriou } 57429d1dc62SVincent Abriou 57529d1dc62SVincent Abriou end: 57629d1dc62SVincent Abriou plane->status = STI_PLANE_UPDATED; 57729d1dc62SVincent Abriou } 57829d1dc62SVincent Abriou 57929d1dc62SVincent Abriou static void sti_gdp_atomic_disable(struct drm_plane *drm_plane, 58029d1dc62SVincent Abriou struct drm_plane_state *oldstate) 58129d1dc62SVincent Abriou { 58229d1dc62SVincent Abriou struct sti_plane *plane = to_sti_plane(drm_plane); 58329d1dc62SVincent Abriou struct sti_mixer *mixer = to_sti_mixer(drm_plane->crtc); 58429d1dc62SVincent Abriou 58529d1dc62SVincent Abriou if (!drm_plane->crtc) { 58629d1dc62SVincent Abriou DRM_DEBUG_DRIVER("drm plane:%d not enabled\n", 58729d1dc62SVincent Abriou drm_plane->base.id); 58829d1dc62SVincent Abriou return; 58929d1dc62SVincent Abriou } 59029d1dc62SVincent Abriou 59129d1dc62SVincent Abriou DRM_DEBUG_DRIVER("CRTC:%d (%s) drm plane:%d (%s)\n", 59229d1dc62SVincent Abriou drm_plane->crtc->base.id, sti_mixer_to_str(mixer), 59329d1dc62SVincent Abriou drm_plane->base.id, sti_plane_to_str(plane)); 59429d1dc62SVincent Abriou 59529d1dc62SVincent Abriou plane->status = STI_PLANE_DISABLING; 59629d1dc62SVincent Abriou } 59729d1dc62SVincent Abriou 59829d1dc62SVincent Abriou static const struct drm_plane_helper_funcs sti_gdp_helpers_funcs = { 59929d1dc62SVincent Abriou .atomic_update = sti_gdp_atomic_update, 60029d1dc62SVincent Abriou .atomic_disable = sti_gdp_atomic_disable, 601ba2d53fbSBenjamin Gaignard }; 602ba2d53fbSBenjamin Gaignard 60329d1dc62SVincent Abriou struct drm_plane *sti_gdp_create(struct drm_device *drm_dev, 60429d1dc62SVincent Abriou struct device *dev, int desc, 60529d1dc62SVincent Abriou void __iomem *baseaddr, 60629d1dc62SVincent Abriou unsigned int possible_crtcs, 60729d1dc62SVincent Abriou enum drm_plane_type type) 608ba2d53fbSBenjamin Gaignard { 609ba2d53fbSBenjamin Gaignard struct sti_gdp *gdp; 61029d1dc62SVincent Abriou int res; 611ba2d53fbSBenjamin Gaignard 612ba2d53fbSBenjamin Gaignard gdp = devm_kzalloc(dev, sizeof(*gdp), GFP_KERNEL); 613ba2d53fbSBenjamin Gaignard if (!gdp) { 614ba2d53fbSBenjamin Gaignard DRM_ERROR("Failed to allocate memory for GDP\n"); 615ba2d53fbSBenjamin Gaignard return NULL; 616ba2d53fbSBenjamin Gaignard } 617ba2d53fbSBenjamin Gaignard 618871bcdfeSVincent Abriou gdp->dev = dev; 619871bcdfeSVincent Abriou gdp->regs = baseaddr; 620871bcdfeSVincent Abriou gdp->plane.desc = desc; 62129d1dc62SVincent Abriou gdp->plane.status = STI_PLANE_DISABLED; 622871bcdfeSVincent Abriou 623ba2d53fbSBenjamin Gaignard gdp->vtg_field_nb.notifier_call = sti_gdp_field_cb; 624ba2d53fbSBenjamin Gaignard 625871bcdfeSVincent Abriou sti_gdp_init(gdp); 626871bcdfeSVincent Abriou 62729d1dc62SVincent Abriou res = drm_universal_plane_init(drm_dev, &gdp->plane.drm_plane, 62829d1dc62SVincent Abriou possible_crtcs, 62929d1dc62SVincent Abriou &sti_plane_helpers_funcs, 63029d1dc62SVincent Abriou gdp_supported_formats, 63129d1dc62SVincent Abriou ARRAY_SIZE(gdp_supported_formats), 632b0b3b795SVille Syrjälä type, NULL); 63329d1dc62SVincent Abriou if (res) { 63429d1dc62SVincent Abriou DRM_ERROR("Failed to initialize universal plane\n"); 63529d1dc62SVincent Abriou goto err; 63629d1dc62SVincent Abriou } 63729d1dc62SVincent Abriou 63829d1dc62SVincent Abriou drm_plane_helper_add(&gdp->plane.drm_plane, &sti_gdp_helpers_funcs); 63929d1dc62SVincent Abriou 64029d1dc62SVincent Abriou sti_plane_init_property(&gdp->plane, type); 64129d1dc62SVincent Abriou 64229d1dc62SVincent Abriou return &gdp->plane.drm_plane; 64329d1dc62SVincent Abriou 64429d1dc62SVincent Abriou err: 64529d1dc62SVincent Abriou devm_kfree(dev, gdp); 64629d1dc62SVincent Abriou return NULL; 647ba2d53fbSBenjamin Gaignard } 648