| 1 | /* $NetBSD: radeon_pci.c,v 1.10 2015/05/29 05:48:46 mrg Exp $ */ |
| 2 | |
| 3 | /*- |
| 4 | * Copyright (c) 2014 The NetBSD Foundation, Inc. |
| 5 | * All rights reserved. |
| 6 | * |
| 7 | * This code is derived from software contributed to The NetBSD Foundation |
| 8 | * by Taylor R. Campbell. |
| 9 | * |
| 10 | * Redistribution and use in source and binary forms, with or without |
| 11 | * modification, are permitted provided that the following conditions |
| 12 | * are met: |
| 13 | * 1. Redistributions of source code must retain the above copyright |
| 14 | * notice, this list of conditions and the following disclaimer. |
| 15 | * 2. Redistributions in binary form must reproduce the above copyright |
| 16 | * notice, this list of conditions and the following disclaimer in the |
| 17 | * documentation and/or other materials provided with the distribution. |
| 18 | * |
| 19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
| 20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| 21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
| 23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 29 | * POSSIBILITY OF SUCH DAMAGE. |
| 30 | */ |
| 31 | |
| 32 | #include <sys/cdefs.h> |
| 33 | __KERNEL_RCSID(0, "$NetBSD: radeon_pci.c,v 1.10 2015/05/29 05:48:46 mrg Exp $" ); |
| 34 | |
| 35 | #ifdef _KERNEL_OPT |
| 36 | #include "vga.h" |
| 37 | #endif |
| 38 | |
| 39 | #include <sys/types.h> |
| 40 | #include <sys/queue.h> |
| 41 | #include <sys/systm.h> |
| 42 | #include <sys/workqueue.h> |
| 43 | |
| 44 | #include <dev/pci/pciio.h> |
| 45 | #include <dev/pci/pcireg.h> |
| 46 | #include <dev/pci/pcivar.h> |
| 47 | |
| 48 | #include <dev/pci/wsdisplay_pci.h> |
| 49 | #include <dev/wsfb/genfbvar.h> |
| 50 | |
| 51 | #if NVGA > 0 |
| 52 | /* |
| 53 | * XXX All we really need is vga_is_console from vgavar.h, but the |
| 54 | * header files are missing their own dependencies, so we need to |
| 55 | * explicitly drag in the other crap. |
| 56 | */ |
| 57 | #include <dev/ic/mc6845reg.h> |
| 58 | #include <dev/ic/pcdisplayvar.h> |
| 59 | #include <dev/ic/vgareg.h> |
| 60 | #include <dev/ic/vgavar.h> |
| 61 | #endif |
| 62 | |
| 63 | #include <drm/drmP.h> |
| 64 | #include <drm/drm_fb_helper.h> |
| 65 | |
| 66 | #include <radeon.h> |
| 67 | #include "radeon_drv.h" |
| 68 | #include "radeon_task.h" |
| 69 | |
| 70 | SIMPLEQ_HEAD(radeon_task_head, radeon_task); |
| 71 | |
| 72 | struct radeon_softc { |
| 73 | device_t sc_dev; |
| 74 | struct pci_attach_args sc_pa; |
| 75 | enum { |
| 76 | RADEON_TASK_ATTACH, |
| 77 | RADEON_TASK_WORKQUEUE, |
| 78 | } sc_task_state; |
| 79 | union { |
| 80 | struct workqueue *workqueue; |
| 81 | struct radeon_task_head attach; |
| 82 | } sc_task_u; |
| 83 | struct drm_device *sc_drm_dev; |
| 84 | struct pci_dev sc_pci_dev; |
| 85 | #if defined(__i386__) |
| 86 | #define RADEON_PCI_UGLY_MAP_HACK |
| 87 | /* XXX Used to claim the VGA device before attach_real */ |
| 88 | bus_space_handle_t sc_temp_memh; |
| 89 | bool sc_temp_set; |
| 90 | #endif |
| 91 | }; |
| 92 | |
| 93 | struct radeon_device * |
| 94 | radeon_device_private(device_t self) |
| 95 | { |
| 96 | struct radeon_softc *const sc = device_private(self); |
| 97 | |
| 98 | return sc->sc_drm_dev->dev_private; |
| 99 | } |
| 100 | |
| 101 | static bool radeon_pci_lookup(const struct pci_attach_args *, |
| 102 | unsigned long *); |
| 103 | |
| 104 | static int radeon_match(device_t, cfdata_t, void *); |
| 105 | static void radeon_attach(device_t, device_t, void *); |
| 106 | static void radeon_attach_real(device_t); |
| 107 | static int radeon_detach(device_t, int); |
| 108 | static bool radeon_do_suspend(device_t, const pmf_qual_t *); |
| 109 | static bool radeon_do_resume(device_t, const pmf_qual_t *); |
| 110 | |
| 111 | static void radeon_task_work(struct work *, void *); |
| 112 | |
| 113 | CFATTACH_DECL_NEW(radeon, sizeof(struct radeon_softc), |
| 114 | radeon_match, radeon_attach, radeon_detach, NULL); |
| 115 | |
| 116 | /* XXX Kludge to get these from radeon_drv.c. */ |
| 117 | extern struct drm_driver *const radeon_drm_driver; |
| 118 | extern const struct pci_device_id *const radeon_device_ids; |
| 119 | extern const size_t radeon_n_device_ids; |
| 120 | |
| 121 | /* Set this to false if you want to match R100/R200 */ |
| 122 | bool radeon_pci_ignore_r100_r200 = true; |
| 123 | |
| 124 | static bool |
| 125 | radeon_pci_lookup(const struct pci_attach_args *pa, unsigned long *flags) |
| 126 | { |
| 127 | size_t i; |
| 128 | enum radeon_family fam; |
| 129 | |
| 130 | for (i = 0; i < radeon_n_device_ids; i++) { |
| 131 | if ((PCI_VENDOR(pa->pa_id) == radeon_device_ids[i].vendor) && |
| 132 | (PCI_PRODUCT(pa->pa_id) == radeon_device_ids[i].device)) |
| 133 | break; |
| 134 | } |
| 135 | |
| 136 | /* Did we find it? */ |
| 137 | if (i == radeon_n_device_ids) |
| 138 | return false; |
| 139 | |
| 140 | /* NetBSD drm2 fails on R100 and many R200 chipsets, disable for now */ |
| 141 | fam = radeon_device_ids[i].driver_data & RADEON_FAMILY_MASK; |
| 142 | if (radeon_pci_ignore_r100_r200 && fam < CHIP_RV280) |
| 143 | return false; |
| 144 | |
| 145 | if (flags) |
| 146 | *flags = radeon_device_ids[i].driver_data; |
| 147 | return true; |
| 148 | } |
| 149 | |
| 150 | static int |
| 151 | radeon_match(device_t parent, cfdata_t match, void *aux) |
| 152 | { |
| 153 | extern int radeon_guarantee_initialized(void); |
| 154 | const struct pci_attach_args *const pa = aux; |
| 155 | int error; |
| 156 | |
| 157 | error = radeon_guarantee_initialized(); |
| 158 | if (error) { |
| 159 | aprint_error("radeon: failed to initialize: %d\n" , error); |
| 160 | return 0; |
| 161 | } |
| 162 | |
| 163 | if (!radeon_pci_lookup(pa, NULL)) |
| 164 | return 0; |
| 165 | |
| 166 | return 6; /* XXX Beat genfb_pci... */ |
| 167 | } |
| 168 | |
| 169 | static void |
| 170 | radeon_attach(device_t parent, device_t self, void *aux) |
| 171 | { |
| 172 | struct radeon_softc *const sc = device_private(self); |
| 173 | const struct pci_attach_args *const pa = aux; |
| 174 | |
| 175 | pci_aprint_devinfo(pa, NULL); |
| 176 | |
| 177 | if (!pmf_device_register(self, &radeon_do_suspend, &radeon_do_resume)) |
| 178 | aprint_error_dev(self, "unable to establish power handler\n" ); |
| 179 | |
| 180 | /* |
| 181 | * Trivial initialization first; the rest will come after we |
| 182 | * have mounted the root file system and can load firmware |
| 183 | * images. |
| 184 | */ |
| 185 | sc->sc_dev = NULL; |
| 186 | sc->sc_pa = *pa; |
| 187 | |
| 188 | #ifdef RADEON_PCI_UGLY_MAP_HACK |
| 189 | /* |
| 190 | * XXX |
| 191 | * We try to map the VGA registers, in case we can prevent vga@isa or |
| 192 | * pcdisplay@isa attaching, and stealing wsdisplay0. This only works |
| 193 | * with serial console, as actual VGA console has already mapped them. |
| 194 | * The only way to handle that is for vga@isa to not attach. |
| 195 | */ |
| 196 | int rv = bus_space_map(pa->pa_memt, 0xb0000, 0x10000, 0, |
| 197 | &sc->sc_temp_memh); |
| 198 | sc->sc_temp_set = rv == 0; |
| 199 | if (rv != 0) |
| 200 | aprint_error_dev(self, "unable to reserve VGA registers for " |
| 201 | "i386 radeondrmkms hack\n" ); |
| 202 | #endif |
| 203 | |
| 204 | config_mountroot(self, &radeon_attach_real); |
| 205 | } |
| 206 | |
| 207 | static void |
| 208 | radeon_attach_real(device_t self) |
| 209 | { |
| 210 | struct radeon_softc *const sc = device_private(self); |
| 211 | const struct pci_attach_args *const pa = &sc->sc_pa; |
| 212 | bool ok __diagused; |
| 213 | unsigned long flags; |
| 214 | int error; |
| 215 | |
| 216 | ok = radeon_pci_lookup(pa, &flags); |
| 217 | KASSERT(ok); |
| 218 | |
| 219 | #ifdef RADEON_PCI_UGLY_MAP_HACK |
| 220 | /* |
| 221 | * XXX |
| 222 | * Unmap the VGA registers. |
| 223 | */ |
| 224 | if (sc->sc_temp_set) |
| 225 | bus_space_unmap(pa->pa_memt, sc->sc_temp_memh, 0x10000); |
| 226 | #endif |
| 227 | |
| 228 | sc->sc_task_state = RADEON_TASK_ATTACH; |
| 229 | SIMPLEQ_INIT(&sc->sc_task_u.attach); |
| 230 | |
| 231 | /* XXX errno Linux->NetBSD */ |
| 232 | error = -drm_pci_attach(self, pa, &sc->sc_pci_dev, radeon_drm_driver, |
| 233 | flags, &sc->sc_drm_dev); |
| 234 | if (error) { |
| 235 | aprint_error_dev(self, "unable to attach drm: %d\n" , error); |
| 236 | goto out; |
| 237 | } |
| 238 | |
| 239 | while (!SIMPLEQ_EMPTY(&sc->sc_task_u.attach)) { |
| 240 | struct radeon_task *const task = |
| 241 | SIMPLEQ_FIRST(&sc->sc_task_u.attach); |
| 242 | |
| 243 | SIMPLEQ_REMOVE_HEAD(&sc->sc_task_u.attach, rt_u.queue); |
| 244 | (*task->rt_fn)(task); |
| 245 | } |
| 246 | |
| 247 | sc->sc_task_state = RADEON_TASK_WORKQUEUE; |
| 248 | error = workqueue_create(&sc->sc_task_u.workqueue, "radeonfb" , |
| 249 | &radeon_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE); |
| 250 | if (error) { |
| 251 | aprint_error_dev(self, "unable to create workqueue: %d\n" , |
| 252 | error); |
| 253 | sc->sc_task_u.workqueue = NULL; |
| 254 | goto out; |
| 255 | } |
| 256 | |
| 257 | out: sc->sc_dev = self; |
| 258 | } |
| 259 | |
| 260 | static int |
| 261 | radeon_detach(device_t self, int flags) |
| 262 | { |
| 263 | struct radeon_softc *const sc = device_private(self); |
| 264 | int error; |
| 265 | |
| 266 | if (sc->sc_dev == NULL) |
| 267 | /* Not done attaching. */ |
| 268 | return EBUSY; |
| 269 | |
| 270 | /* XXX Check for in-use before tearing it all down... */ |
| 271 | error = config_detach_children(self, flags); |
| 272 | if (error) |
| 273 | return error; |
| 274 | |
| 275 | if (sc->sc_task_state == RADEON_TASK_ATTACH) |
| 276 | goto out; |
| 277 | if (sc->sc_task_u.workqueue != NULL) { |
| 278 | workqueue_destroy(sc->sc_task_u.workqueue); |
| 279 | sc->sc_task_u.workqueue = NULL; |
| 280 | } |
| 281 | |
| 282 | if (sc->sc_drm_dev == NULL) |
| 283 | goto out; |
| 284 | /* XXX errno Linux->NetBSD */ |
| 285 | error = -drm_pci_detach(sc->sc_drm_dev, flags); |
| 286 | if (error) |
| 287 | /* XXX Kinda too late to fail now... */ |
| 288 | return error; |
| 289 | sc->sc_drm_dev = NULL; |
| 290 | |
| 291 | out: pmf_device_deregister(self); |
| 292 | |
| 293 | return 0; |
| 294 | } |
| 295 | |
| 296 | static bool |
| 297 | radeon_do_suspend(device_t self, const pmf_qual_t *qual) |
| 298 | { |
| 299 | struct radeon_softc *const sc = device_private(self); |
| 300 | struct drm_device *const dev = sc->sc_drm_dev; |
| 301 | int ret; |
| 302 | bool is_console = true; /* XXX */ |
| 303 | |
| 304 | if (dev == NULL) |
| 305 | return true; |
| 306 | |
| 307 | ret = radeon_suspend_kms(dev, true, is_console); |
| 308 | if (ret) |
| 309 | return false; |
| 310 | |
| 311 | return true; |
| 312 | } |
| 313 | |
| 314 | static bool |
| 315 | radeon_do_resume(device_t self, const pmf_qual_t *qual) |
| 316 | { |
| 317 | struct radeon_softc *const sc = device_private(self); |
| 318 | struct drm_device *const dev = sc->sc_drm_dev; |
| 319 | int ret; |
| 320 | bool is_console = true; /* XXX */ |
| 321 | |
| 322 | if (dev == NULL) |
| 323 | return true; |
| 324 | |
| 325 | ret = radeon_resume_kms(dev, true, is_console); |
| 326 | if (ret) |
| 327 | return false; |
| 328 | |
| 329 | return true; |
| 330 | } |
| 331 | |
| 332 | static void |
| 333 | radeon_task_work(struct work *work, void *cookie __unused) |
| 334 | { |
| 335 | struct radeon_task *const task = container_of(work, struct radeon_task, |
| 336 | rt_u.work); |
| 337 | |
| 338 | (*task->rt_fn)(task); |
| 339 | } |
| 340 | |
| 341 | int |
| 342 | radeon_task_schedule(device_t self, struct radeon_task *task) |
| 343 | { |
| 344 | struct radeon_softc *const sc = device_private(self); |
| 345 | |
| 346 | switch (sc->sc_task_state) { |
| 347 | case RADEON_TASK_ATTACH: |
| 348 | SIMPLEQ_INSERT_TAIL(&sc->sc_task_u.attach, task, rt_u.queue); |
| 349 | return 0; |
| 350 | case RADEON_TASK_WORKQUEUE: |
| 351 | if (sc->sc_task_u.workqueue == NULL) { |
| 352 | aprint_error_dev(self, "unable to schedule task\n" ); |
| 353 | return EIO; |
| 354 | } |
| 355 | workqueue_enqueue(sc->sc_task_u.workqueue, &task->rt_u.work, |
| 356 | NULL); |
| 357 | return 0; |
| 358 | default: |
| 359 | panic("radeon in invalid task state: %d\n" , |
| 360 | (int)sc->sc_task_state); |
| 361 | } |
| 362 | } |
| 363 | |