Merge tag 'csky-for-linus-5.12-rc1' of git://github.com/c-sky/csky-linux
[linux-2.6-microblaze.git] / drivers / gpu / drm / bochs / bochs_hw.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  */
4
5 #include <linux/pci.h>
6
7 #include <drm/drm_drv.h>
8 #include <drm/drm_fourcc.h>
9
10 #include "bochs.h"
11
12 /* ---------------------------------------------------------------------- */
13
14 static void bochs_vga_writeb(struct bochs_device *bochs, u16 ioport, u8 val)
15 {
16         if (WARN_ON(ioport < 0x3c0 || ioport > 0x3df))
17                 return;
18
19         if (bochs->mmio) {
20                 int offset = ioport - 0x3c0 + 0x400;
21                 writeb(val, bochs->mmio + offset);
22         } else {
23                 outb(val, ioport);
24         }
25 }
26
27 static u16 bochs_dispi_read(struct bochs_device *bochs, u16 reg)
28 {
29         u16 ret = 0;
30
31         if (bochs->mmio) {
32                 int offset = 0x500 + (reg << 1);
33                 ret = readw(bochs->mmio + offset);
34         } else {
35                 outw(reg, VBE_DISPI_IOPORT_INDEX);
36                 ret = inw(VBE_DISPI_IOPORT_DATA);
37         }
38         return ret;
39 }
40
41 static void bochs_dispi_write(struct bochs_device *bochs, u16 reg, u16 val)
42 {
43         if (bochs->mmio) {
44                 int offset = 0x500 + (reg << 1);
45                 writew(val, bochs->mmio + offset);
46         } else {
47                 outw(reg, VBE_DISPI_IOPORT_INDEX);
48                 outw(val, VBE_DISPI_IOPORT_DATA);
49         }
50 }
51
52 static void bochs_hw_set_big_endian(struct bochs_device *bochs)
53 {
54         if (bochs->qext_size < 8)
55                 return;
56
57         writel(0xbebebebe, bochs->mmio + 0x604);
58 }
59
60 static void bochs_hw_set_little_endian(struct bochs_device *bochs)
61 {
62         if (bochs->qext_size < 8)
63                 return;
64
65         writel(0x1e1e1e1e, bochs->mmio + 0x604);
66 }
67
68 #ifdef __BIG_ENDIAN
69 #define bochs_hw_set_native_endian(_b) bochs_hw_set_big_endian(_b)
70 #else
71 #define bochs_hw_set_native_endian(_b) bochs_hw_set_little_endian(_b)
72 #endif
73
74 static int bochs_get_edid_block(void *data, u8 *buf,
75                                 unsigned int block, size_t len)
76 {
77         struct bochs_device *bochs = data;
78         size_t i, start = block * EDID_LENGTH;
79
80         if (start + len > 0x400 /* vga register offset */)
81                 return -1;
82
83         for (i = 0; i < len; i++) {
84                 buf[i] = readb(bochs->mmio + start + i);
85         }
86         return 0;
87 }
88
89 int bochs_hw_load_edid(struct bochs_device *bochs)
90 {
91         u8 header[8];
92
93         if (!bochs->mmio)
94                 return -1;
95
96         /* check header to detect whenever edid support is enabled in qemu */
97         bochs_get_edid_block(bochs, header, 0, ARRAY_SIZE(header));
98         if (drm_edid_header_is_valid(header) != 8)
99                 return -1;
100
101         kfree(bochs->edid);
102         bochs->edid = drm_do_get_edid(&bochs->connector,
103                                       bochs_get_edid_block, bochs);
104         if (bochs->edid == NULL)
105                 return -1;
106
107         return 0;
108 }
109
110 int bochs_hw_init(struct drm_device *dev)
111 {
112         struct bochs_device *bochs = dev->dev_private;
113         struct pci_dev *pdev = to_pci_dev(dev->dev);
114         unsigned long addr, size, mem, ioaddr, iosize;
115         u16 id;
116
117         if (pdev->resource[2].flags & IORESOURCE_MEM) {
118                 /* mmio bar with vga and bochs registers present */
119                 if (pci_request_region(pdev, 2, "bochs-drm") != 0) {
120                         DRM_ERROR("Cannot request mmio region\n");
121                         return -EBUSY;
122                 }
123                 ioaddr = pci_resource_start(pdev, 2);
124                 iosize = pci_resource_len(pdev, 2);
125                 bochs->mmio = ioremap(ioaddr, iosize);
126                 if (bochs->mmio == NULL) {
127                         DRM_ERROR("Cannot map mmio region\n");
128                         return -ENOMEM;
129                 }
130         } else {
131                 ioaddr = VBE_DISPI_IOPORT_INDEX;
132                 iosize = 2;
133                 if (!request_region(ioaddr, iosize, "bochs-drm")) {
134                         DRM_ERROR("Cannot request ioports\n");
135                         return -EBUSY;
136                 }
137                 bochs->ioports = 1;
138         }
139
140         id = bochs_dispi_read(bochs, VBE_DISPI_INDEX_ID);
141         mem = bochs_dispi_read(bochs, VBE_DISPI_INDEX_VIDEO_MEMORY_64K)
142                 * 64 * 1024;
143         if ((id & 0xfff0) != VBE_DISPI_ID0) {
144                 DRM_ERROR("ID mismatch\n");
145                 return -ENODEV;
146         }
147
148         if ((pdev->resource[0].flags & IORESOURCE_MEM) == 0)
149                 return -ENODEV;
150         addr = pci_resource_start(pdev, 0);
151         size = pci_resource_len(pdev, 0);
152         if (addr == 0)
153                 return -ENODEV;
154         if (size != mem) {
155                 DRM_ERROR("Size mismatch: pci=%ld, bochs=%ld\n",
156                         size, mem);
157                 size = min(size, mem);
158         }
159
160         if (pci_request_region(pdev, 0, "bochs-drm") != 0)
161                 DRM_WARN("Cannot request framebuffer, boot fb still active?\n");
162
163         bochs->fb_map = ioremap(addr, size);
164         if (bochs->fb_map == NULL) {
165                 DRM_ERROR("Cannot map framebuffer\n");
166                 return -ENOMEM;
167         }
168         bochs->fb_base = addr;
169         bochs->fb_size = size;
170
171         DRM_INFO("Found bochs VGA, ID 0x%x.\n", id);
172         DRM_INFO("Framebuffer size %ld kB @ 0x%lx, %s @ 0x%lx.\n",
173                  size / 1024, addr,
174                  bochs->ioports ? "ioports" : "mmio",
175                  ioaddr);
176
177         if (bochs->mmio && pdev->revision >= 2) {
178                 bochs->qext_size = readl(bochs->mmio + 0x600);
179                 if (bochs->qext_size < 4 || bochs->qext_size > iosize) {
180                         bochs->qext_size = 0;
181                         goto noext;
182                 }
183                 DRM_DEBUG("Found qemu ext regs, size %ld\n",
184                           bochs->qext_size);
185                 bochs_hw_set_native_endian(bochs);
186         }
187
188 noext:
189         return 0;
190 }
191
192 void bochs_hw_fini(struct drm_device *dev)
193 {
194         struct bochs_device *bochs = dev->dev_private;
195
196         /* TODO: shot down existing vram mappings */
197
198         if (bochs->mmio)
199                 iounmap(bochs->mmio);
200         if (bochs->ioports)
201                 release_region(VBE_DISPI_IOPORT_INDEX, 2);
202         if (bochs->fb_map)
203                 iounmap(bochs->fb_map);
204         pci_release_regions(to_pci_dev(dev->dev));
205         kfree(bochs->edid);
206 }
207
208 void bochs_hw_setmode(struct bochs_device *bochs,
209                       struct drm_display_mode *mode)
210 {
211         int idx;
212
213         if (!drm_dev_enter(bochs->dev, &idx))
214                 return;
215
216         bochs->xres = mode->hdisplay;
217         bochs->yres = mode->vdisplay;
218         bochs->bpp = 32;
219         bochs->stride = mode->hdisplay * (bochs->bpp / 8);
220         bochs->yres_virtual = bochs->fb_size / bochs->stride;
221
222         DRM_DEBUG_DRIVER("%dx%d @ %d bpp, vy %d\n",
223                          bochs->xres, bochs->yres, bochs->bpp,
224                          bochs->yres_virtual);
225
226         bochs_vga_writeb(bochs, 0x3c0, 0x20); /* unblank */
227
228         bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE,      0);
229         bochs_dispi_write(bochs, VBE_DISPI_INDEX_BPP,         bochs->bpp);
230         bochs_dispi_write(bochs, VBE_DISPI_INDEX_XRES,        bochs->xres);
231         bochs_dispi_write(bochs, VBE_DISPI_INDEX_YRES,        bochs->yres);
232         bochs_dispi_write(bochs, VBE_DISPI_INDEX_BANK,        0);
233         bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_WIDTH,  bochs->xres);
234         bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_HEIGHT,
235                           bochs->yres_virtual);
236         bochs_dispi_write(bochs, VBE_DISPI_INDEX_X_OFFSET,    0);
237         bochs_dispi_write(bochs, VBE_DISPI_INDEX_Y_OFFSET,    0);
238
239         bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE,
240                           VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED);
241
242         drm_dev_exit(idx);
243 }
244
245 void bochs_hw_setformat(struct bochs_device *bochs,
246                         const struct drm_format_info *format)
247 {
248         int idx;
249
250         if (!drm_dev_enter(bochs->dev, &idx))
251                 return;
252
253         DRM_DEBUG_DRIVER("format %c%c%c%c\n",
254                          (format->format >>  0) & 0xff,
255                          (format->format >>  8) & 0xff,
256                          (format->format >> 16) & 0xff,
257                          (format->format >> 24) & 0xff);
258
259         switch (format->format) {
260         case DRM_FORMAT_XRGB8888:
261                 bochs_hw_set_little_endian(bochs);
262                 break;
263         case DRM_FORMAT_BGRX8888:
264                 bochs_hw_set_big_endian(bochs);
265                 break;
266         default:
267                 /* should not happen */
268                 DRM_ERROR("%s: Huh? Got framebuffer format 0x%x",
269                           __func__, format->format);
270                 break;
271         }
272
273         drm_dev_exit(idx);
274 }
275
276 void bochs_hw_setbase(struct bochs_device *bochs,
277                       int x, int y, int stride, u64 addr)
278 {
279         unsigned long offset;
280         unsigned int vx, vy, vwidth, idx;
281
282         if (!drm_dev_enter(bochs->dev, &idx))
283                 return;
284
285         bochs->stride = stride;
286         offset = (unsigned long)addr +
287                 y * bochs->stride +
288                 x * (bochs->bpp / 8);
289         vy = offset / bochs->stride;
290         vx = (offset % bochs->stride) * 8 / bochs->bpp;
291         vwidth = stride * 8 / bochs->bpp;
292
293         DRM_DEBUG_DRIVER("x %d, y %d, addr %llx -> offset %lx, vx %d, vy %d\n",
294                          x, y, addr, offset, vx, vy);
295         bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_WIDTH, vwidth);
296         bochs_dispi_write(bochs, VBE_DISPI_INDEX_X_OFFSET, vx);
297         bochs_dispi_write(bochs, VBE_DISPI_INDEX_Y_OFFSET, vy);
298
299         drm_dev_exit(idx);
300 }