root/drivers/staging/sm750fb/sm750_hw.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/vmalloc.h>
#include <linux/pagemap.h>
#include <linux/console.h>
#ifdef CONFIG_MTRR
#include <asm/mtrr.h>
#endif
#include <linux/platform_device.h>
#include <linux/sizes.h>

#include "sm750.h"
#include "ddk750.h"
#include "sm750_accel.h"

void __iomem *mmio750;

int hw_sm750_map(struct sm750_dev *sm750_dev, struct pci_dev *pdev)
{
        int ret;

        ret = 0;

        sm750_dev->vidreg_start = pci_resource_start(pdev, 1);
        sm750_dev->vidreg_size = SZ_2M;

        pr_info("mmio phyAddr = %lx\n", sm750_dev->vidreg_start);

        /* reserve the vidreg space of smi adaptor */
        ret = pci_request_region(pdev, 1, "sm750fb");
        if (ret) {
                pr_err("Can not request PCI regions.\n");
                return ret;
        }

        /* now map mmio and vidmem */
        sm750_dev->pvReg =
                ioremap(sm750_dev->vidreg_start, sm750_dev->vidreg_size);
        if (!sm750_dev->pvReg) {
                pr_err("mmio failed\n");
                ret = -EFAULT;
                goto err_release_region;
        }
        pr_info("mmio virtual addr = %p\n", sm750_dev->pvReg);

        sm750_dev->accel.dpr_base = sm750_dev->pvReg + DE_BASE_ADDR_TYPE1;
        sm750_dev->accel.dp_port_base = sm750_dev->pvReg + DE_PORT_ADDR_TYPE1;

        mmio750 = sm750_dev->pvReg;
        sm750_set_chip_type(sm750_dev->devid, sm750_dev->revid);

        sm750_dev->vidmem_start = pci_resource_start(pdev, 0);
        /*
         * don't use pdev_resource[x].end - resource[x].start to
         * calculate the resource size, it's only the maximum available
         * size but not the actual size, using
         * @ddk750_get_vm_size function can be safe.
         */
        sm750_dev->vidmem_size = ddk750_get_vm_size();
        pr_info("video memory phyAddr = %lx, size = %u bytes\n",
                sm750_dev->vidmem_start, sm750_dev->vidmem_size);

        /* reserve the vidmem space of smi adaptor */
        sm750_dev->pvMem =
                ioremap_wc(sm750_dev->vidmem_start, sm750_dev->vidmem_size);
        if (!sm750_dev->pvMem) {
                pr_err("Map video memory failed\n");
                ret = -EFAULT;
                goto err_unmap_reg;
        }
        pr_info("video memory vaddr = %p\n", sm750_dev->pvMem);

        return 0;

err_unmap_reg:
        iounmap(sm750_dev->pvReg);
err_release_region:
        pci_release_region(pdev, 1);
        return ret;
}

int hw_sm750_inithw(struct sm750_dev *sm750_dev, struct pci_dev *pdev)
{
        struct init_status *parm;

        parm = &sm750_dev->init_parm;
        if (parm->chip_clk == 0)
                parm->chip_clk = (sm750_get_chip_type() == SM750LE) ?
                                               DEFAULT_SM750LE_CHIP_CLOCK :
                                               DEFAULT_SM750_CHIP_CLOCK;

        if (parm->mem_clk == 0)
                parm->mem_clk = parm->chip_clk;
        if (parm->master_clk == 0)
                parm->master_clk = parm->chip_clk / 3;

        ddk750_init_hw((struct initchip_param *)&sm750_dev->init_parm);
        /* for sm718, open pci burst */
        if (sm750_dev->devid == 0x718) {
                poke32(SYSTEM_CTRL,
                       peek32(SYSTEM_CTRL) | SYSTEM_CTRL_PCI_BURST);
        }

        if (sm750_get_chip_type() != SM750LE) {
                unsigned int val;
                /* does user need CRT? */
                if (sm750_dev->nocrt) {
                        poke32(MISC_CTRL,
                               peek32(MISC_CTRL) | MISC_CTRL_DAC_POWER_OFF);
                        /* shut off dpms */
                        val = peek32(SYSTEM_CTRL) & ~SYSTEM_CTRL_DPMS_MASK;
                        val |= SYSTEM_CTRL_DPMS_VPHN;
                        poke32(SYSTEM_CTRL, val);
                } else {
                        poke32(MISC_CTRL,
                               peek32(MISC_CTRL) & ~MISC_CTRL_DAC_POWER_OFF);
                        /* turn on dpms */
                        val = peek32(SYSTEM_CTRL) & ~SYSTEM_CTRL_DPMS_MASK;
                        val |= SYSTEM_CTRL_DPMS_VPHP;
                        poke32(SYSTEM_CTRL, val);
                }

                val = peek32(PANEL_DISPLAY_CTRL) &
                      ~(PANEL_DISPLAY_CTRL_DUAL_DISPLAY |
                        PANEL_DISPLAY_CTRL_DOUBLE_PIXEL);
                switch (sm750_dev->pnltype) {
                case sm750_24TFT:
                        break;
                case sm750_doubleTFT:
                        val |= PANEL_DISPLAY_CTRL_DOUBLE_PIXEL;
                        break;
                case sm750_dualTFT:
                        val |= PANEL_DISPLAY_CTRL_DUAL_DISPLAY;
                        break;
                }
                poke32(PANEL_DISPLAY_CTRL, val);
        } else {
                /*
                 * for 750LE, no DVI chip initialization
                 * makes Monitor no signal
                 *
                 * Set up GPIO for software I2C to program DVI chip in the
                 * Xilinx SP605 board, in order to have video signal.
                 */
                sm750_sw_i2c_init(0, 1);

                /*
                 * Customer may NOT use CH7301 DVI chip, which has to be
                 * initialized differently.
                 */
                if (sm750_sw_i2c_read_reg(0xec, 0x4a) == 0x95) {
                        /*
                         * The following register values for CH7301 are from
                         * Chrontel app note and our experiment.
                         */
                        pr_info("yes,CH7301 DVI chip found\n");
                        sm750_sw_i2c_write_reg(0xec, 0x1d, 0x16);
                        sm750_sw_i2c_write_reg(0xec, 0x21, 0x9);
                        sm750_sw_i2c_write_reg(0xec, 0x49, 0xC0);
                        pr_info("okay,CH7301 DVI chip setup done\n");
                }
        }

        /* init 2d engine */
        if (!sm750_dev->accel_off)
                hw_sm750_init_accel(sm750_dev);

        return 0;
}

int hw_sm750_output_set_mode(struct lynxfb_output *output,
                             struct fb_var_screeninfo *var,
                             struct fb_fix_screeninfo *fix)
{
        int ret;
        enum disp_output disp_set;
        int channel;

        ret = 0;
        disp_set = 0;
        channel = *output->channel;

        if (sm750_get_chip_type() != SM750LE) {
                if (channel == sm750_primary) {
                        pr_info("primary channel\n");
                        if (output->paths & sm750_panel)
                                disp_set |= do_LCD1_PRI;
                        if (output->paths & sm750_crt)
                                disp_set |= do_CRT_PRI;

                } else {
                        pr_info("secondary channel\n");
                        if (output->paths & sm750_panel)
                                disp_set |= do_LCD1_SEC;
                        if (output->paths & sm750_crt)
                                disp_set |= do_CRT_SEC;
                }
                ddk750_set_logical_disp_out(disp_set);
        } else {
                /* just open DISPLAY_CONTROL_750LE register bit 3:0 */
                u32 reg;

                reg = peek32(DISPLAY_CONTROL_750LE);
                reg |= 0xf;
                poke32(DISPLAY_CONTROL_750LE, reg);
        }

        pr_info("ddk setlogicdispout done\n");
        return ret;
}

int hw_sm750_crtc_check_mode(struct lynxfb_crtc *crtc,
                             struct fb_var_screeninfo *var)
{
        struct sm750_dev *sm750_dev;
        struct lynxfb_par *par = container_of(crtc, struct lynxfb_par, crtc);

        sm750_dev = par->dev;

        switch (var->bits_per_pixel) {
        case 8:
        case 16:
                break;
        case 32:
                if (sm750_dev->revid == SM750LE_REVISION_ID) {
                        pr_debug("750le do not support 32bpp\n");
                        return -EINVAL;
                }
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

/* set the controller's mode for @crtc charged with @var and @fix parameters */
int hw_sm750_crtc_set_mode(struct lynxfb_crtc *crtc,
                           struct fb_var_screeninfo *var,
                           struct fb_fix_screeninfo *fix)
{
        int ret, fmt;
        u32 reg;
        struct mode_parameter modparm;
        enum clock_type clock;
        struct sm750_dev *sm750_dev;
        struct lynxfb_par *par;

        ret = 0;
        par = container_of(crtc, struct lynxfb_par, crtc);
        sm750_dev = par->dev;

        if (!sm750_dev->accel_off) {
                /* set 2d engine pixel format according to mode bpp */
                switch (var->bits_per_pixel) {
                case 8:
                        fmt = 0;
                        break;
                case 16:
                        fmt = 1;
                        break;
                case 32:
                default:
                        fmt = 2;
                        break;
                }
                sm750_hw_set2dformat(&sm750_dev->accel, fmt);
        }

        /* set timing */
        modparm.pixel_clock = ps_to_hz(var->pixclock);
        modparm.vertical_sync_polarity =
                (var->sync & FB_SYNC_HOR_HIGH_ACT) ? POS : NEG;
        modparm.horizontal_sync_polarity =
                (var->sync & FB_SYNC_VERT_HIGH_ACT) ? POS : NEG;
        modparm.clock_phase_polarity =
                (var->sync & FB_SYNC_COMP_HIGH_ACT) ? POS : NEG;
        modparm.horizontal_display_end = var->xres;
        modparm.horizontal_sync_width = var->hsync_len;
        modparm.horizontal_sync_start = var->xres + var->right_margin;
        modparm.horizontal_total = var->xres + var->left_margin +
                                   var->right_margin + var->hsync_len;
        modparm.vertical_display_end = var->yres;
        modparm.vertical_sync_height = var->vsync_len;
        modparm.vertical_sync_start = var->yres + var->lower_margin;
        modparm.vertical_total = var->yres + var->upper_margin +
                                 var->lower_margin + var->vsync_len;

        /* choose pll */
        if (crtc->channel != sm750_secondary)
                clock = PRIMARY_PLL;
        else
                clock = SECONDARY_PLL;

        pr_debug("Request pixel clock = %lu\n", modparm.pixel_clock);
        ret = ddk750_set_mode_timing(&modparm, clock);
        if (ret) {
                pr_err("Set mode timing failed\n");
                goto exit;
        }

        if (crtc->channel != sm750_secondary) {
                /* set pitch, offset, width, start address, etc... */
                poke32(PANEL_FB_ADDRESS,
                       crtc->o_screen & PANEL_FB_ADDRESS_ADDRESS_MASK);

                reg = var->xres * (var->bits_per_pixel >> 3);
                /*
                 * crtc->channel is not equal to par->index on numeric,
                 * be aware of that
                 */
                reg = ALIGN(reg, crtc->line_pad);
                reg = (reg << PANEL_FB_WIDTH_WIDTH_SHIFT) &
                      PANEL_FB_WIDTH_WIDTH_MASK;
                reg |= (fix->line_length & PANEL_FB_WIDTH_OFFSET_MASK);
                poke32(PANEL_FB_WIDTH, reg);

                reg = ((var->xres - 1) << PANEL_WINDOW_WIDTH_WIDTH_SHIFT) &
                      PANEL_WINDOW_WIDTH_WIDTH_MASK;
                reg |= (var->xoffset & PANEL_WINDOW_WIDTH_X_MASK);
                poke32(PANEL_WINDOW_WIDTH, reg);

                reg = (var->yres_virtual - 1)
                      << PANEL_WINDOW_HEIGHT_HEIGHT_SHIFT;
                reg &= PANEL_WINDOW_HEIGHT_HEIGHT_MASK;
                reg |= (var->yoffset & PANEL_WINDOW_HEIGHT_Y_MASK);
                poke32(PANEL_WINDOW_HEIGHT, reg);

                poke32(PANEL_PLANE_TL, 0);

                reg = ((var->yres - 1) << PANEL_PLANE_BR_BOTTOM_SHIFT) &
                      PANEL_PLANE_BR_BOTTOM_MASK;
                reg |= ((var->xres - 1) & PANEL_PLANE_BR_RIGHT_MASK);
                poke32(PANEL_PLANE_BR, reg);

                /* set pixel format */
                reg = peek32(PANEL_DISPLAY_CTRL);
                poke32(PANEL_DISPLAY_CTRL, reg | (var->bits_per_pixel >> 4));
        } else {
                /* not implemented now */
                poke32(CRT_FB_ADDRESS, crtc->o_screen);
                reg = var->xres * (var->bits_per_pixel >> 3);
                /*
                 * crtc->channel is not equal to par->index on numeric,
                 * be aware of that
                 */
                reg = ALIGN(reg, crtc->line_pad) << CRT_FB_WIDTH_WIDTH_SHIFT;
                reg &= CRT_FB_WIDTH_WIDTH_MASK;
                reg |= (fix->line_length & CRT_FB_WIDTH_OFFSET_MASK);
                poke32(CRT_FB_WIDTH, reg);

                /* SET PIXEL FORMAT */
                reg = peek32(CRT_DISPLAY_CTRL);
                reg |= ((var->bits_per_pixel >> 4) &
                        CRT_DISPLAY_CTRL_FORMAT_MASK);
                poke32(CRT_DISPLAY_CTRL, reg);
        }

exit:
        return ret;
}

int hw_sm750_set_col_reg(struct lynxfb_crtc *crtc, ushort index, ushort red,
                         ushort green, ushort blue)
{
        static unsigned int add[] = { PANEL_PALETTE_RAM, CRT_PALETTE_RAM };

        poke32(add[crtc->channel] + index * 4,
               (red << 16) | (green << 8) | blue);
        return 0;
}

int hw_sm750le_set_blank(struct lynxfb_output *output, int blank)
{
        int dpms, crtdb;

        switch (blank) {
        case FB_BLANK_UNBLANK:
                dpms = CRT_DISPLAY_CTRL_DPMS_0;
                crtdb = 0;
                break;
        case FB_BLANK_NORMAL:
                dpms = CRT_DISPLAY_CTRL_DPMS_0;
                crtdb = CRT_DISPLAY_CTRL_BLANK;
                break;
        case FB_BLANK_VSYNC_SUSPEND:
                dpms = CRT_DISPLAY_CTRL_DPMS_2;
                crtdb = CRT_DISPLAY_CTRL_BLANK;
                break;
        case FB_BLANK_HSYNC_SUSPEND:
                dpms = CRT_DISPLAY_CTRL_DPMS_1;
                crtdb = CRT_DISPLAY_CTRL_BLANK;
                break;
        case FB_BLANK_POWERDOWN:
                dpms = CRT_DISPLAY_CTRL_DPMS_3;
                crtdb = CRT_DISPLAY_CTRL_BLANK;
                break;
        default:
                return -EINVAL;
        }

        if (output->paths & sm750_crt) {
                unsigned int val;

                val = peek32(CRT_DISPLAY_CTRL) & ~CRT_DISPLAY_CTRL_DPMS_MASK;
                poke32(CRT_DISPLAY_CTRL, val | dpms);

                val = peek32(CRT_DISPLAY_CTRL) & ~CRT_DISPLAY_CTRL_BLANK;
                poke32(CRT_DISPLAY_CTRL, val | crtdb);
        }
        return 0;
}

int hw_sm750_set_blank(struct lynxfb_output *output, int blank)
{
        unsigned int dpms, pps, crtdb;

        dpms = 0;
        pps = 0;
        crtdb = 0;

        switch (blank) {
        case FB_BLANK_UNBLANK:
                pr_debug("flag = FB_BLANK_UNBLANK\n");
                dpms = SYSTEM_CTRL_DPMS_VPHP;
                pps = PANEL_DISPLAY_CTRL_DATA;
                break;
        case FB_BLANK_NORMAL:
                pr_debug("flag = FB_BLANK_NORMAL\n");
                dpms = SYSTEM_CTRL_DPMS_VPHP;
                crtdb = CRT_DISPLAY_CTRL_BLANK;
                break;
        case FB_BLANK_VSYNC_SUSPEND:
                dpms = SYSTEM_CTRL_DPMS_VNHP;
                crtdb = CRT_DISPLAY_CTRL_BLANK;
                break;
        case FB_BLANK_HSYNC_SUSPEND:
                dpms = SYSTEM_CTRL_DPMS_VPHN;
                crtdb = CRT_DISPLAY_CTRL_BLANK;
                break;
        case FB_BLANK_POWERDOWN:
                dpms = SYSTEM_CTRL_DPMS_VNHN;
                crtdb = CRT_DISPLAY_CTRL_BLANK;
                break;
        }

        if (output->paths & sm750_crt) {
                unsigned int val = peek32(SYSTEM_CTRL) & ~SYSTEM_CTRL_DPMS_MASK;

                poke32(SYSTEM_CTRL, val | dpms);

                val = peek32(CRT_DISPLAY_CTRL) & ~CRT_DISPLAY_CTRL_BLANK;
                poke32(CRT_DISPLAY_CTRL, val | crtdb);
        }

        if (output->paths & sm750_panel) {
                unsigned int val = peek32(PANEL_DISPLAY_CTRL);

                val &= ~PANEL_DISPLAY_CTRL_DATA;
                val |= pps;
                poke32(PANEL_DISPLAY_CTRL, val);
        }

        return 0;
}

void hw_sm750_init_accel(struct sm750_dev *sm750_dev)
{
        u32 reg;

        sm750_enable_2d_engine(1);

        if (sm750_get_chip_type() == SM750LE) {
                reg = peek32(DE_STATE1);
                reg |= DE_STATE1_DE_ABORT;
                poke32(DE_STATE1, reg);

                reg = peek32(DE_STATE1);
                reg &= ~DE_STATE1_DE_ABORT;
                poke32(DE_STATE1, reg);

        } else {
                /* engine reset */
                reg = peek32(SYSTEM_CTRL);
                reg |= SYSTEM_CTRL_DE_ABORT;
                poke32(SYSTEM_CTRL, reg);

                reg = peek32(SYSTEM_CTRL);
                reg &= ~SYSTEM_CTRL_DE_ABORT;
                poke32(SYSTEM_CTRL, reg);
        }

        /* call 2d init */
        sm750_dev->accel.de_init(&sm750_dev->accel);
}

int hw_sm750le_de_wait(void)
{
        int i = 0x10000000;
        unsigned int mask = DE_STATE2_DE_STATUS_BUSY | DE_STATE2_DE_FIFO_EMPTY |
                            DE_STATE2_DE_MEM_FIFO_EMPTY;

        while (i--) {
                unsigned int val = peek32(DE_STATE2);

                if ((val & mask) ==
                    (DE_STATE2_DE_FIFO_EMPTY | DE_STATE2_DE_MEM_FIFO_EMPTY))
                        return 0;
        }
        /* timeout error */
        return -1;
}

int hw_sm750_de_wait(void)
{
        int i = 0x10000000;
        unsigned int mask = SYSTEM_CTRL_DE_STATUS_BUSY |
                            SYSTEM_CTRL_DE_FIFO_EMPTY |
                            SYSTEM_CTRL_DE_MEM_FIFO_EMPTY;

        while (i--) {
                unsigned int val = peek32(SYSTEM_CTRL);

                if ((val & mask) ==
                    (SYSTEM_CTRL_DE_FIFO_EMPTY | SYSTEM_CTRL_DE_MEM_FIFO_EMPTY))
                        return 0;
        }
        /* timeout error */
        return -1;
}

int hw_sm750_pan_display(struct lynxfb_crtc *crtc,
                         const struct fb_var_screeninfo *var,
                         const struct fb_info *info)
{
        u32 total;
        /* check params */
        if ((var->xoffset + var->xres > var->xres_virtual) ||
            (var->yoffset + var->yres > var->yres_virtual)) {
                return -EINVAL;
        }

        total = var->yoffset * info->fix.line_length +
                ((var->xoffset * var->bits_per_pixel) >> 3);
        total += crtc->o_screen;
        if (crtc->channel == sm750_primary) {
                poke32(PANEL_FB_ADDRESS,
                       peek32(PANEL_FB_ADDRESS) |
                               (total & PANEL_FB_ADDRESS_ADDRESS_MASK));
        } else {
                poke32(CRT_FB_ADDRESS,
                       peek32(CRT_FB_ADDRESS) |
                               (total & CRT_FB_ADDRESS_ADDRESS_MASK));
        }
        return 0;
}