linux-bk/drivers/scsi/aic7xxx/aic7xxx_osm_pci.c
<<
>>
Prefs
   1/*
   2 * Linux driver attachment glue for PCI based controllers.
   3 *
   4 * Copyright (c) 2000-2001 Adaptec Inc.
   5 * All rights reserved.
   6 *
   7 * Redistribution and use in source and binary forms, with or without
   8 * modification, are permitted provided that the following conditions
   9 * are met:
  10 * 1. Redistributions of source code must retain the above copyright
  11 *    notice, this list of conditions, and the following disclaimer,
  12 *    without modification.
  13 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
  14 *    substantially similar to the "NO WARRANTY" disclaimer below
  15 *    ("Disclaimer") and any redistribution must be conditioned upon
  16 *    including a substantially similar Disclaimer requirement for further
  17 *    binary redistribution.
  18 * 3. Neither the names of the above-listed copyright holders nor the names
  19 *    of any contributors may be used to endorse or promote products derived
  20 *    from this software without specific prior written permission.
  21 *
  22 * Alternatively, this software may be distributed under the terms of the
  23 * GNU General Public License ("GPL") version 2 as published by the Free
  24 * Software Foundation.
  25 *
  26 * NO WARRANTY
  27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  28 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  29 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
  30 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  31 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  35 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
  36 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  37 * POSSIBILITY OF SUCH DAMAGES.
  38 *
  39 * $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic7xxx_osm_pci.c#47 $
  40 */
  41
  42#include "aic7xxx_osm.h"
  43
  44#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
  45struct pci_device_id
  46{
  47};
  48#endif
  49
  50static int      ahc_linux_pci_dev_probe(struct pci_dev *pdev,
  51                                        const struct pci_device_id *ent);
  52static int      ahc_linux_pci_reserve_io_region(struct ahc_softc *ahc,
  53                                                u_long *base);
  54static int      ahc_linux_pci_reserve_mem_region(struct ahc_softc *ahc,
  55                                                 u_long *bus_addr,
  56                                                 uint8_t **maddr);
  57#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
  58static void     ahc_linux_pci_dev_remove(struct pci_dev *pdev);
  59
  60/* We do our own ID filtering.  So, grab all SCSI storage class devices. */
  61static struct pci_device_id ahc_linux_pci_id_table[] = {
  62        {
  63                0x9004, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
  64                PCI_CLASS_STORAGE_SCSI << 8, 0xFFFF00, 0
  65        },
  66        {
  67                0x9005, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
  68                PCI_CLASS_STORAGE_SCSI << 8, 0xFFFF00, 0
  69        },
  70        { 0 }
  71};
  72
  73MODULE_DEVICE_TABLE(pci, ahc_linux_pci_id_table);
  74
  75struct pci_driver aic7xxx_pci_driver = {
  76        .name           = "aic7xxx",
  77        .probe          = ahc_linux_pci_dev_probe,
  78        .remove         = ahc_linux_pci_dev_remove,
  79        .id_table       = ahc_linux_pci_id_table
  80};
  81
  82static void
  83ahc_linux_pci_dev_remove(struct pci_dev *pdev)
  84{
  85        struct ahc_softc *ahc;
  86        u_long l;
  87
  88        /*
  89         * We should be able to just perform
  90         * the free directly, but check our
  91         * list for extra sanity.
  92         */
  93        ahc_list_lock(&l);
  94        ahc = ahc_find_softc((struct ahc_softc *)pci_get_drvdata(pdev));
  95        if (ahc != NULL) {
  96                u_long s;
  97
  98                ahc_lock(ahc, &s);
  99                ahc_intr_enable(ahc, FALSE);
 100                ahc_unlock(ahc, &s);
 101                ahc_free(ahc);
 102        }
 103        ahc_list_unlock(&l);
 104}
 105#endif /* !LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0) */
 106
 107static int
 108ahc_linux_pci_dev_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 109{
 110        char             buf[80];
 111        bus_addr_t       mask_39bit;
 112        struct           ahc_softc *ahc;
 113        ahc_dev_softc_t  pci;
 114        struct           ahc_pci_identity *entry;
 115        char            *name;
 116        int              error;
 117
 118        /*
 119         * Some BIOSen report the same device multiple times.
 120         */
 121        TAILQ_FOREACH(ahc, &ahc_tailq, links) {
 122                struct pci_dev *probed_pdev;
 123
 124                probed_pdev = ahc->dev_softc;
 125                if (probed_pdev->bus->number == pdev->bus->number
 126                 && probed_pdev->devfn == pdev->devfn)
 127                        break;
 128        }
 129        if (ahc != NULL) {
 130                /* Skip duplicate. */
 131                return (-ENODEV);
 132        }
 133
 134        pci = pdev;
 135        entry = ahc_find_pci_device(pci);
 136        if (entry == NULL)
 137                return (-ENODEV);
 138
 139        /*
 140         * Allocate a softc for this card and
 141         * set it up for attachment by our
 142         * common detect routine.
 143         */
 144        sprintf(buf, "ahc_pci:%d:%d:%d",
 145                ahc_get_pci_bus(pci),
 146                ahc_get_pci_slot(pci),
 147                ahc_get_pci_function(pci));
 148        name = malloc(strlen(buf) + 1, M_DEVBUF, M_NOWAIT);
 149        if (name == NULL)
 150                return (-ENOMEM);
 151        strcpy(name, buf);
 152        ahc = ahc_alloc(NULL, name);
 153        if (ahc == NULL)
 154                return (-ENOMEM);
 155#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
 156        if (pci_enable_device(pdev)) {
 157                ahc_free(ahc);
 158                return (-ENODEV);
 159        }
 160        pci_set_master(pdev);
 161
 162        mask_39bit = (bus_addr_t)0x7FFFFFFFFFULL;
 163        if (sizeof(bus_addr_t) > 4
 164         && ahc_linux_get_memsize() > 0x80000000
 165         && ahc_pci_set_dma_mask(pdev, mask_39bit) == 0) {
 166                ahc->flags |= AHC_39BIT_ADDRESSING;
 167                ahc->platform_data->hw_dma_mask = mask_39bit;
 168        } else {
 169                if (ahc_pci_set_dma_mask(pdev, 0xFFFFFFFF)) {
 170                        printk(KERN_WARNING "aic7xxx: No suitable DMA available.\n");
 171                        return (-ENODEV);
 172                }
 173                ahc->platform_data->hw_dma_mask = 0xFFFFFFFF;
 174        }
 175#endif
 176        ahc->dev_softc = pci;
 177        error = ahc_pci_config(ahc, entry);
 178        if (error != 0) {
 179                ahc_free(ahc);
 180                return (-error);
 181        }
 182#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
 183        pci_set_drvdata(pdev, ahc);
 184        if (aic7xxx_detect_complete) {
 185#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
 186                ahc_linux_register_host(ahc, &aic7xxx_driver_template);
 187#else
 188                printf("aic7xxx: ignoring PCI device found after "
 189                       "initialization\n");
 190                return (-ENODEV);
 191#endif
 192        }
 193#endif
 194        return (0);
 195}
 196
 197int
 198ahc_linux_pci_init(void)
 199{
 200#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
 201        return (pci_module_init(&aic7xxx_pci_driver));
 202#else
 203        struct pci_dev *pdev;
 204        u_int class;
 205        int found;
 206
 207        /* If we don't have a PCI bus, we can't find any adapters. */
 208        if (pci_present() == 0)
 209                return (0);
 210
 211        found = 0;
 212        pdev = NULL;
 213        class = PCI_CLASS_STORAGE_SCSI << 8;
 214        while ((pdev = pci_find_class(class, pdev)) != NULL) {
 215                ahc_dev_softc_t pci;
 216                int error;
 217
 218                pci = pdev;
 219                error = ahc_linux_pci_dev_probe(pdev, /*pci_devid*/NULL);
 220                if (error == 0)
 221                        found++;
 222        }
 223        return (found);
 224#endif
 225}
 226
 227void
 228ahc_linux_pci_exit(void)
 229{
 230        pci_unregister_driver(&aic7xxx_pci_driver);
 231}
 232
 233static int
 234ahc_linux_pci_reserve_io_region(struct ahc_softc *ahc, u_long *base)
 235{
 236        if (aic7xxx_allow_memio == 0)
 237                return (ENOMEM);
 238
 239#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
 240        *base = pci_resource_start(ahc->dev_softc, 0);
 241#else
 242        *base = ahc_pci_read_config(ahc->dev_softc, PCIR_MAPS, 4);
 243        *base &= PCI_BASE_ADDRESS_IO_MASK;
 244#endif
 245        if (*base == 0)
 246                return (ENOMEM);
 247#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
 248        if (check_region(*base, 256) != 0)
 249                return (ENOMEM);
 250        request_region(*base, 256, "aic7xxx");
 251#else
 252        if (request_region(*base, 256, "aic7xxx") == 0)
 253                return (ENOMEM);
 254#endif
 255        return (0);
 256}
 257
 258static int
 259ahc_linux_pci_reserve_mem_region(struct ahc_softc *ahc,
 260                                 u_long *bus_addr,
 261                                 uint8_t **maddr)
 262{
 263        u_long  start;
 264        u_long  base_page;
 265        u_long  base_offset;
 266        int     error;
 267
 268        error = 0;
 269#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
 270        start = pci_resource_start(ahc->dev_softc, 1);
 271        base_page = start & PAGE_MASK;
 272        base_offset = start - base_page;
 273#else
 274        start = ahc_pci_read_config(ahc->dev_softc, PCIR_MAPS+4, 4);
 275        base_offset = start & PCI_BASE_ADDRESS_MEM_MASK;
 276        base_page = base_offset & PAGE_MASK;
 277        base_offset -= base_page;
 278#endif
 279        if (start != 0) {
 280                *bus_addr = start;
 281#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
 282                if (request_mem_region(start, 0x1000, "aic7xxx") == 0)
 283                        error = ENOMEM;
 284#endif
 285                if (error == 0) {
 286                        *maddr = ioremap_nocache(base_page, base_offset + 256);
 287                        if (*maddr == NULL) {
 288                                error = ENOMEM;
 289#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
 290                                release_mem_region(start, 0x1000);
 291#endif
 292                        } else
 293                                *maddr += base_offset;
 294                }
 295        } else
 296                error = ENOMEM;
 297        return (error);
 298}
 299
 300int
 301ahc_pci_map_registers(struct ahc_softc *ahc)
 302{
 303        uint32_t command;
 304        u_long   base;
 305        uint8_t *maddr;
 306        int      error;
 307
 308        /*
 309         * If its allowed, we prefer memory mapped access.
 310         */
 311        command = ahc_pci_read_config(ahc->dev_softc, PCIR_COMMAND, 4);
 312        command &= ~(PCIM_CMD_PORTEN|PCIM_CMD_MEMEN);
 313        base = 0;
 314        maddr = NULL;
 315        error = ahc_linux_pci_reserve_mem_region(ahc, &base, &maddr);
 316        if (error == 0) {
 317                ahc->platform_data->mem_busaddr = base;
 318                ahc->tag = BUS_SPACE_MEMIO;
 319                ahc->bsh.maddr = maddr;
 320                ahc_pci_write_config(ahc->dev_softc, PCIR_COMMAND,
 321                                     command | PCIM_CMD_MEMEN, 4);
 322
 323                /*
 324                 * Do a quick test to see if memory mapped
 325                 * I/O is functioning correctly.
 326                 */
 327                if (ahc_pci_test_register_access(ahc) != 0) {
 328
 329                        printf("aic7xxx: PCI Device %d:%d:%d "
 330                               "failed memory mapped test.  Using PIO.\n",
 331                               ahc_get_pci_bus(ahc->dev_softc),
 332                               ahc_get_pci_slot(ahc->dev_softc),
 333                               ahc_get_pci_function(ahc->dev_softc));
 334                        iounmap((void *)((u_long)maddr & PAGE_MASK));
 335#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
 336                        release_mem_region(ahc->platform_data->mem_busaddr,
 337                                           0x1000);
 338#endif
 339                        ahc->bsh.maddr = NULL;
 340                        maddr = NULL;
 341                } else
 342                        command |= PCIM_CMD_MEMEN;
 343        } else {
 344                printf("aic7xxx: PCI%d:%d:%d MEM region 0x%lx "
 345                       "unavailable. Cannot memory map device.\n",
 346                       ahc_get_pci_bus(ahc->dev_softc),
 347                       ahc_get_pci_slot(ahc->dev_softc),
 348                       ahc_get_pci_function(ahc->dev_softc),
 349                       base);
 350        }
 351
 352        /*
 353         * We always prefer memory mapped access.
 354         */
 355        if (maddr == NULL) {
 356
 357                error = ahc_linux_pci_reserve_io_region(ahc, &base);
 358                if (error == 0) {
 359                        ahc->tag = BUS_SPACE_PIO;
 360                        ahc->bsh.ioport = base;
 361                        command |= PCIM_CMD_PORTEN;
 362                } else {
 363                        printf("aic7xxx: PCI%d:%d:%d IO region 0x%lx[0..255] "
 364                               "unavailable. Cannot map device.\n",
 365                               ahc_get_pci_bus(ahc->dev_softc),
 366                               ahc_get_pci_slot(ahc->dev_softc),
 367                               ahc_get_pci_function(ahc->dev_softc),
 368                               base);
 369                }
 370        }
 371        ahc_pci_write_config(ahc->dev_softc, PCIR_COMMAND, command, 4);
 372        return (error);
 373}
 374
 375int
 376ahc_pci_map_int(struct ahc_softc *ahc)
 377{
 378        int error;
 379
 380        error = request_irq(ahc->dev_softc->irq, ahc_linux_isr,
 381                            SA_SHIRQ, "aic7xxx", ahc);
 382        if (error == 0)
 383                ahc->platform_data->irq = ahc->dev_softc->irq;
 384        
 385        return (-error);
 386}
 387
 388void
 389ahc_power_state_change(struct ahc_softc *ahc, ahc_power_state new_state)
 390{
 391#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
 392        pci_set_power_state(ahc->dev_softc, new_state);
 393#else
 394        uint32_t cap;
 395        u_int cap_offset;
 396
 397        /*
 398         * Traverse the capability list looking for
 399         * the power management capability.
 400         */
 401        cap = 0;
 402        cap_offset = ahc_pci_read_config(ahc->dev_softc,
 403                                         PCIR_CAP_PTR, /*bytes*/1);
 404        while (cap_offset != 0) {
 405
 406                cap = ahc_pci_read_config(ahc->dev_softc,
 407                                          cap_offset, /*bytes*/4);
 408                if ((cap & 0xFF) == 1
 409                 && ((cap >> 16) & 0x3) > 0) {
 410                        uint32_t pm_control;
 411
 412                        pm_control = ahc_pci_read_config(ahc->dev_softc,
 413                                                         cap_offset + 4,
 414                                                         /*bytes*/4);
 415                        pm_control &= ~0x3;
 416                        pm_control |= new_state;
 417                        ahc_pci_write_config(ahc->dev_softc,
 418                                             cap_offset + 4,
 419                                             pm_control, /*bytes*/2);
 420                        break;
 421                }
 422                cap_offset = (cap >> 8) & 0xFF;
 423        }
 424#endif 
 425}
 426
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.