/* linux/drivers/mfd/sm501.c * * Copyright (C) 2006 Simtec Electronics * Ben Dooks <ben@simtec.co.uk> * Vincent Sanders <vince@simtec.co.uk> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * SM501 MFD driver */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/list.h> #include <linux/device.h> #include <linux/platform_device.h> #include <linux/pci.h> #include <linux/sm501.h> #include <linux/sm501-regs.h> #include <asm/io.h> struct sm501_device { struct list_head list; struct platform_device pdev; }; struct sm501_devdata { spinlock_t reg_lock; struct mutex clock_lock; struct list_head devices; struct device *dev; struct resource *io_res; struct resource *mem_res; struct resource *regs_claim; struct sm501_platdata *platdata; int unit_power[20]; unsigned int pdev_id; unsigned int irq; void __iomem *regs; }; #define MHZ (1000 * 1000) #ifdef DEBUG static const unsigned int misc_div[] = { [0] = 1, [1] = 2, [2] = 4, [3] = 8, [4] = 16, [5] = 32, [6] = 64, [7] = 128, [8] = 3, [9] = 6, [10] = 12, [11] = 24, [12] = 48, [13] = 96, [14] = 192, [15] = 384, }; static const unsigned int px_div[] = { [0] = 1, [1] = 2, [2] = 4, [3] = 8, [4] = 16, [5] = 32, [6] = 64, [7] = 128, [8] = 3, [9] = 6, [10] = 12, [11] = 24, [12] = 48, [13] = 96, [14] = 192, [15] = 384, [16] = 5, [17] = 10, [18] = 20, [19] = 40, [20] = 80, [21] = 160, [22] = 320, [23] = 604, }; static unsigned long decode_div(unsigned long pll2, unsigned long val, unsigned int lshft, unsigned int selbit, unsigned long mask, const unsigned int *dtab) { if (val & selbit) pll2 = 288 * MHZ; return pll2 / dtab[(val >> lshft) & mask]; } #define fmt_freq(x) ((x) / MHZ), ((x) % MHZ), (x) /* sm501_dump_clk * * Print out the current clock configuration for the device */ static void sm501_dump_clk(struct sm501_devdata *sm) { unsigned long misct = readl(sm->regs + SM501_MISC_TIMING); unsigned long pm0 = readl(sm->regs + SM501_POWER_MODE_0_CLOCK); unsigned long pm1 = readl(sm->regs + SM501_POWER_MODE_1_CLOCK); unsigned long pmc = readl(sm->regs + SM501_POWER_MODE_CONTROL); unsigned long sdclk0, sdclk1; unsigned long pll2 = 0; switch (misct & 0x30) { case 0x00: pll2 = 336 * MHZ; break; case 0x10: pll2 = 288 * MHZ; break; case 0x20: pll2 = 240 * MHZ; break; case 0x30: pll2 = 192 * MHZ; break; } sdclk0 = (misct & (1<<12)) ? pll2 : 288 * MHZ; sdclk0 /= misc_div[((misct >> 8) & 0xf)]; sdclk1 = (misct & (1<<20)) ? pll2 : 288 * MHZ; sdclk1 /= misc_div[((misct >> 16) & 0xf)]; dev_dbg(sm->dev, "MISCT=%08lx, PM0=%08lx, PM1=%08lx\n", misct, pm0, pm1); dev_dbg(sm->dev, "PLL2 = %ld.%ld MHz (%ld), SDCLK0=%08lx, SDCLK1=%08lx\n", fmt_freq(pll2), sdclk0, sdclk1); dev_dbg(sm->dev, "SDRAM: PM0=%ld, PM1=%ld\n", sdclk0, sdclk1); dev_dbg(sm->dev, "PM0[%c]: " "P2 %ld.%ld MHz (%ld), V2 %ld.%ld (%ld), " x "M %ld.%ld (%ld), MX1 %ld.%ld (%ld)\n", (pmc & 3 ) == 0 ? '*' : '-', fmt_freq(decode_div(pll2, pm0, 24, 1<<29, 31, px_div)), fmt_freq(decode_div(pll2, pm0, 16, 1<<20, 15, misc_div)), fmt_freq(decode_div(pll2, pm0, 8, 1<<12, 15, misc_div)), fmt_freq(decode_div(pll2, pm0, 0, 1<<4, 15, misc_div))); dev_dbg(sm->dev, "PM1[%c]: " "P2 %ld.%ld MHz (%ld), V2 %ld.%ld (%ld), " "M %ld.%ld (%ld), MX1 %ld.%ld (%ld)\n", (pmc & 3 ) == 1 ? '*' : '-', fmt_freq(decode_div(pll2, pm1, 24, 1<<29, 31, px_div)), fmt_freq(decode_div(pll2, pm1, 16, 1<<20, 15, misc_div)), fmt_freq(decode_div(pll2, pm1, 8, 1<<12, 15, misc_div)), fmt_freq(decode_div(pll2, pm1, 0, 1<<4, 15, misc_div))); } #else static void sm501_dump_clk(struct sm501_devdata *sm) { } #endif /* sm501_sync_regs * * ensure the */ static void sm501_sync_regs(struct sm501_devdata *sm) { readl(sm->regs); } /* sm501_misc_control * * alters the misceleneous control parameters */ int sm501_misc_control(struct device *dev, unsigned long set, unsigned long clear) { struct sm501_devdata *sm = dev_get_drvdata(dev); unsigned long misc; unsigned long save; unsigned long to; spin_lock_irqsave(&sm->reg_lock, save); misc = readl(sm->regs + SM501_MISC_CONTROL); to = (misc & ~clear) | set; if (to != misc) { writel(to, sm->regs + SM501_MISC_CONTROL); sm501_sync_regs(sm); dev_dbg(sm->dev, "MISC_CONTROL %08lx\n", misc); } spin_unlock_irqrestore(&sm->reg_lock, save); return to; } EXPORT_SYMBOL_GPL(sm501_misc_control); /* sm501_modify_reg * * Modify a register in the SM501 which may be shared with other * drivers. */ unsigned long sm501_modify_reg(struct device *dev, unsigned long reg, unsigned long set, unsigned long clear) { struct sm501_devdata *sm = dev_get_drvdata(dev); unsigned long data; unsigned long save; spin_lock_irqsave(&sm->reg_lock, save); data = readl(sm->regs + reg); data |= set; data &= ~clear; writel(data, sm->regs + reg); sm501_sync_regs(sm); spin_unlock_irqrestore(&sm->reg_lock, save); return data; } EXPORT_SYMBOL_GPL(sm501_modify_reg); unsigned long sm501_gpio_get(struct device *dev, unsigned long gpio) { struct sm501_devdata *sm = dev_get_drvdata(dev); unsigned long result; unsigned long reg; reg = (gpio > 32) ? SM501_GPIO_DATA_HIGH : SM501_GPIO_DATA_LOW; result = readl(sm->regs + reg); result >>= (gpio & 31); return result & 1UL; } EXPORT_SYMBOL_GPL(sm501_gpio_get); void sm501_gpio_set(struct device *dev, unsigned long gpio, unsigned int to, unsigned int dir) { struct sm501_devdata *sm = dev_get_drvdata(dev); unsigned long bit = 1 << (gpio & 31); unsigned long base; unsigned long save; unsigned long val; base = (gpio > 32) ? SM501_GPIO_DATA_HIGH : SM501_GPIO_DATA_LOW; base += SM501_GPIO; spin_lock_irqsave(&sm->reg_lock, save); val = readl(sm->regs + base) & ~bit; if (to) val |= bit; writel(val, sm->regs + base); val = readl(sm->regs + SM501_GPIO_DDR_LOW) & ~bit; if (dir) val |= bit; writel(val, sm->regs + SM501_GPIO_DDR_LOW); sm501_sync_regs(sm); spin_unlock_irqrestore(&sm->reg_lock, save); } EXPORT_SYMBOL_GPL(sm501_gpio_set); /* sm501_unit_power * * alters the power active gate to set specific units on or off */ int sm501_unit_power(struct device *dev, unsigned int unit, unsigned int to) { struct sm501_devdata *sm = dev_get_drvdata(dev); unsigned long mode; unsigned long gate; unsigned long clock; mutex_lock(&sm->clock_lock); mode = readl(sm->regs + SM501_POWER_MODE_CONTROL); gate = readl(sm->regs + SM501_CURRENT_GATE); clock = readl(sm->regs + SM501_CURRENT_CLOCK); mode &= 3; /* get current power mode */ if (unit >= ARRAY_SIZE(sm->unit_power)) { dev_err(dev, "%s: bad unit %d\n", __FUNCTION__, unit); goto already; } dev_dbg(sm->dev, "%s: unit %d, cur %d, to %d\n", __FUNCTION__, unit, sm->unit_power[unit], to); if (to == 0 && sm->unit_power[unit] == 0) { dev_err(sm->dev, "unit %d is already shutdown\n", unit); goto already; } sm->unit_power[unit] += to ? 1 : -1; to = sm->unit_power[unit] ? 1 : 0; if (to) { if (gate & (1 << unit)) goto already; gate |= (1 << unit); } else { if (!(gate & (1 << unit))) goto already; gate &= ~(1 << unit); } switch (mode) { case 1: writel(gate, sm->regs + SM501_POWER_MODE_0_GATE); writel(clock, sm->regs + SM501_POWER_MODE_0_CLOCK); mode = 0; break; case 2: case 0: writel(gate, sm->regs + SM501_POWER_MODE_1_GATE); writel(clock, sm->regs + SM501_POWER_MODE_1_CLOCK); mode = 1; break; default: return -1; } writel(mode, sm->regs + SM501_POWER_MODE_CONTROL); sm501_sync_regs(sm); dev_dbg(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", gate, clock, mode); msleep(16); already: mutex_unlock(&sm->clock_lock); return gate; } EXPORT_SYMBOL_GPL(sm501_unit_power); /* Perform a rounded division. */ static long sm501fb_round_div(long num, long denom) { /* n / d + 1 / 2 = (2n + d) / 2d */ return (2 * num + denom) / (2 * denom); } /* clock value structure. */ struct sm501_clock { unsigned long mclk; int divider; int shift; }; /* sm501_select_clock * * selects nearest discrete clock frequency the SM501 can achive * the maximum divisor is 3 or 5 */ static unsigned long sm501_select_clock(unsigned long freq, struct sm501_clock *clock, int max_div) { unsigned long mclk; int divider; int shift; long diff; long best_diff = 999999999; /* Try 288MHz and 336MHz clocks. */ for (mclk = 288000000; mclk <= 336000000; mclk += 48000000) { /* try dividers 1 and 3 for CRT and for panel, try divider 5 for panel only.*/ for (divider = 1; divider <= max_div; divider += 2) { /* try all 8 shift values.*/ for (shift = 0; shift < 8; shift++) { /* Calculate difference to requested clock */ diff = sm501fb_round_div(mclk, divider << shift) - freq; if (diff < 0) diff = -diff; /* If it is less than the current, use it */ if (diff < best_diff) { best_diff = diff; clock->mclk = mclk; clock->divider = divider; clock->shift = shift; } } } } /* Return best clock. */ return clock->mclk / (clock->divider << clock->shift); } /* sm501_set_clock * * set one of the four clock sources to the closest available frequency to * the one specified */ unsigned long sm501_set_clock(struct device *dev, int clksrc, unsigned long req_freq) { struct sm501_devdata *sm = dev_get_drvdata(dev); unsigned long mode = readl(sm->regs + SM501_POWER_MODE_CONTROL); unsigned long gate = readl(sm->regs + SM501_CURRENT_GATE); unsigned long clock = readl(sm->regs + SM501_CURRENT_CLOCK); unsigned char reg; unsigned long sm501_freq; /* the actual frequency acheived */ struct sm501_clock to; /* find achivable discrete frequency and setup register value * accordingly, V2XCLK, MCLK and M1XCLK are the same P2XCLK * has an extra bit for the divider */ switch (clksrc) { case SM501_CLOCK_P2XCLK: /* This clock is divided in half so to achive the * requested frequency the value must be multiplied by * 2. This clock also has an additional pre divisor */ sm501_freq = (sm501_select_clock(2 * req_freq, &to, 5) / 2); reg=to.shift & 0x07;/* bottom 3 bits are shift */ if (to.divider == 3) reg |= 0x08; /* /3 divider required */ else if (to.divider == 5) reg |= 0x10; /* /5 divider required */ if (to.mclk != 288000000) reg |= 0x20; /* which mclk pll is source */ break; case SM501_CLOCK_V2XCLK: /* This clock is divided in half so to achive the * requested frequency the value must be multiplied by 2. */ sm501_freq = (sm501_select_clock(2 * req_freq, &to, 3) / 2); reg=to.shift & 0x07; /* bottom 3 bits are shift */ if (to.divider == 3) reg |= 0x08; /* /3 divider required */ if (to.mclk != 288000000) reg |= 0x10; /* which mclk pll is source */ break; case SM501_CLOCK_MCLK: case SM501_CLOCK_M1XCLK: /* These clocks are the same and not further divided */ sm501_freq = sm501_select_clock( req_freq, &to, 3); reg=to.shift & 0x07; /* bottom 3 bits are shift */ if (to.divider == 3) reg |= 0x08; /* /3 divider required */ if (to.mclk != 288000000) reg |= 0x10; /* which mclk pll is source */ break; default: return 0; /* this is bad */ } mutex_lock(&sm->clock_lock); mode = readl(sm->regs + SM501_POWER_MODE_CONTROL); gate = readl(sm->regs + SM501_CURRENT_GATE); clock = readl(sm->regs + SM501_CURRENT_CLOCK); clock = clock & ~(0xFF << clksrc); clock |= reg<<clksrc; mode &= 3; /* find current mode */ switch (mode) { case 1: writel(gate, sm->regs + SM501_POWER_MODE_0_GATE); writel(clock, sm->regs + SM501_POWER_MODE_0_CLOCK); mode = 0; break; case 2: case 0: writel(gate, sm->regs + SM501_POWER_MODE_1_GATE); writel(clock, sm->regs + SM501_POWER_MODE_1_CLOCK); mode = 1; break; default: mutex_unlock(&sm->clock_lock); return -1; } writel(mode, sm->regs + SM501_POWER_MODE_CONTROL); sm501_sync_regs(sm); dev_info(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", gate, clock, mode); msleep(16); mutex_unlock(&sm->clock_lock); sm501_dump_clk(sm); return sm501_freq; } EXPORT_SYMBOL_GPL(sm501_set_clock); /* sm501_find_clock * * finds the closest available frequency for a given clock */ unsigned long sm501_find_clock(int clksrc, unsigned long req_freq) { unsigned long sm501_freq; /* the frequency achiveable by the 501 */ struct sm501_clock to; switch (clksrc) { case SM501_CLOCK_P2XCLK: sm501_freq = (sm501_select_clock(2 * req_freq, &to, 5) / 2); break; case SM501_CLOCK_V2XCLK: sm501_freq = (sm501_select_clock(2 * req_freq, &to, 3) / 2); break; case SM501_CLOCK_MCLK: case SM501_CLOCK_M1XCLK: sm501_freq = sm501_select_clock(req_freq, &to, 3); break; default: sm501_freq = 0; /* error */ } return sm501_freq; } EXPORT_SYMBOL_GPL(sm501_find_clock); static struct sm501_device *to_sm_device(struct platform_device *pdev) { return container_of(pdev, struct sm501_device, pdev); } /* sm501_device_release * * A release function for the platform devices we create to allow us to * free any items we allocated */ static void sm501_device_release(struct device *dev) { kfree(to_sm_device(to_platform_device(dev))); } /* sm501_create_subdev * * Create a skeleton platform device with resources for passing to a * sub-driver */ static struct platform_device * sm501_create_subdev(struct sm501_devdata *sm, char *name, unsigned int res_count) { struct sm501_device *smdev; smdev = kzalloc(sizeof(struct sm501_device) + sizeof(struct resource) * res_count, GFP_KERNEL); if (!smdev) return NULL; smdev->pdev.dev.release = sm501_device_release; smdev->pdev.name = name; smdev->pdev.id = sm->pdev_id; smdev->pdev.resource = (struct resource *)(smdev+1); smdev->pdev.num_resources = res_count; smdev->pdev.dev.parent = sm->dev; return &smdev->pdev; } /* sm501_register_device * * Register a platform device created with sm501_create_subdev() */ static int sm501_register_device(struct sm501_devdata *sm, struct platform_device *pdev) { struct sm501_device *smdev = to_sm_device(pdev); int ptr; int ret; for (ptr = 0; ptr < pdev->num_resources; ptr++) { printk("%s[%d] flags %08lx: %08llx..%08llx\n", pdev->name, ptr, pdev->resource[ptr].flags, (unsigned long long)pdev->resource[ptr].start, (unsigned long long)pdev->resource[ptr].end); } ret = platform_device_register(pdev); if (ret >= 0) { dev_dbg(sm->dev, "registered %s\n", pdev->name); list_add_tail(&smdev->list, &sm->devices); } else dev_err(sm->dev, "error registering %s (%d)\n", pdev->name, ret); return ret; } /* sm501_create_subio * * Fill in an IO resource for a sub device */ static void sm501_create_subio(struct sm501_devdata *sm, struct resource *res, resource_size_t offs, resource_size_t size) { res->flags = IORESOURCE_MEM; res->parent = sm->io_res; res->start = sm->io_res->start + offs; res->end = res->start + size - 1; } /* sm501_create_mem * * Fill in an MEM resource for a sub device */ static void sm501_create_mem(struct sm501_devdata *sm, struct resource *res, resource_size_t *offs, resource_size_t size) { *offs -= size; /* adjust memory size */ res->flags = IORESOURCE_MEM; res->parent = sm->mem_res; res->start = sm->mem_res->start + *offs; res->end = res->start + size - 1; } /* sm501_create_irq * * Fill in an IRQ resource for a sub device */ static void sm501_create_irq(struct sm501_devdata *sm, struct resource *res) { res->flags = IORESOURCE_IRQ; res->parent = NULL; res->start = res->end = sm->irq; } static int sm501_register_usbhost(struct sm501_devdata *sm, resource_size_t *mem_avail) { struct platform_device *pdev; pdev = sm501_create_subdev(sm, "sm501-usb", 3); if (!pdev) return -ENOMEM; sm501_create_subio(sm, &pdev->resource[0], 0x40000, 0x20000); sm501_create_mem(sm, &pdev->resource[1], mem_avail, 256*1024); sm501_create_irq(sm, &pdev->resource[2]); return sm501_register_device(sm, pdev); } static int sm501_register_display(struct sm501_devdata *sm, resource_size_t *mem_avail) { struct platform_device *pdev; pdev = sm501_create_subdev(sm, "sm501-fb", 4); if (!pdev) return -ENOMEM; sm501_create_subio(sm, &pdev->resource[0], 0x80000, 0x10000); sm501_create_subio(sm, &pdev->resource[1], 0x100000, 0x50000); sm501_create_mem(sm, &pdev->resource[2], mem_avail, *mem_avail); sm501_create_irq(sm, &pdev->resource[3]); return sm501_register_device(sm, pdev); } /* sm501_dbg_regs * * Debug attribute to attach to parent device to show core registers */ static ssize_t sm501_dbg_regs(struct device *dev, struct device_attribute *attr, char *buff) { struct sm501_devdata *sm = dev_get_drvdata(dev) ; unsigned int reg; char *ptr = buff; int ret; for (reg = 0x00; reg < 0x70; reg += 4) { ret = sprintf(ptr, "%08x = %08x\n", reg, readl(sm->regs + reg)); ptr += ret; } return ptr - buff; } static DEVICE_ATTR(dbg_regs, 0666, sm501_dbg_regs, NULL); /* sm501_init_reg * * Helper function for the init code to setup a register */ static inline void sm501_init_reg(struct sm501_devdata *sm, unsigned long reg, struct sm501_reg_init *r) { unsigned long tmp; tmp = readl(sm->regs + reg); tmp |= r->set; tmp &= ~r->mask; writel(tmp, sm->regs + reg); } /* sm501_init_regs * * Setup core register values */ static void sm501_init_regs(struct sm501_devdata *sm, struct sm501_initdata *init) { sm501_misc_control(sm->dev, init->misc_control.set, init->misc_control.mask); sm501_init_reg(sm, SM501_MISC_TIMING, &init->misc_timing); sm501_init_reg(sm, SM501_GPIO31_0_CONTROL, &init->gpio_low); sm501_init_reg(sm, SM501_GPIO63_32_CONTROL, &init->gpio_high); if (init->mclk) { dev_info(sm->dev, "setting MCLK to %ld\n", init->mclk); sm501_set_clock(sm->dev, SM501_CLOCK_MCLK, init->mclk); } if (init->m1xclk) { dev_info(sm->dev, "setting M1XCLK to %ld\n", init->m1xclk); sm501_set_clock(sm->dev, SM501_CLOCK_M1XCLK, init->m1xclk); } } static unsigned int sm501_mem_local[] = { [0] = 4*1024*1024, [1] = 8*1024*1024, [2] = 16*1024*1024, [3] = 32*1024*1024, [4] = 64*1024*1024, [5] = 2*1024*1024, }; /* sm501_init_dev * * Common init code for an SM501 */ static int sm501_init_dev(struct sm501_devdata *sm) { resource_size_t mem_avail; unsigned long dramctrl; int ret; mutex_init(&sm->clock_lock); spin_lock_init(&sm->reg_lock); INIT_LIST_HEAD(&sm->devices); dramctrl = readl(sm->regs + SM501_DRAM_CONTROL); mem_avail = sm501_mem_local[(dramctrl >> 13) & 0x7]; dev_info(sm->dev, "SM501 At %p: Version %08x, %ld Mb, IRQ %d\n", sm->regs, readl(sm->regs + SM501_DEVICEID), (unsigned long)mem_avail >> 20, sm->irq); dev_info(sm->dev, "CurrentGate %08x\n", readl(sm->regs+0x38)); dev_info(sm->dev, "CurrentClock %08x\n", readl(sm->regs+0x3c)); dev_info(sm->dev, "PowerModeControl %08x\n", readl(sm->regs+0x54)); ret = device_create_file(sm->dev, &dev_attr_dbg_regs); if (ret) dev_err(sm->dev, "failed to create debug regs file\n"); sm501_dump_clk(sm); /* check to see if we have some device initialisation */ if (sm->platdata) { struct sm501_platdata *pdata = sm->platdata; if (pdata->init) { sm501_init_regs(sm, sm->platdata->init); if (pdata->init->devices & SM501_USE_USB_HOST) sm501_register_usbhost(sm, &mem_avail); } } /* always create a framebuffer */ sm501_register_display(sm, &mem_avail); return 0; } static int sm501_plat_probe(struct platform_device *dev) { struct sm501_devdata *sm; int err; sm = kzalloc(sizeof(struct sm501_devdata), GFP_KERNEL); if (sm == NULL) { dev_err(&dev->dev, "no memory for device data\n"); err = -ENOMEM; goto err1; } sm->dev = &dev->dev; sm->pdev_id = dev->id; sm->irq = platform_get_irq(dev, 0); sm->io_res = platform_get_resource(dev, IORESOURCE_MEM, 1); sm->mem_res = platform_get_resource(dev, IORESOURCE_MEM, 0); sm->platdata = dev->dev.platform_data; if (sm->irq < 0) { dev_err(&dev->dev, "failed to get irq resource\n"); err = sm->irq; goto err_res; } if (sm->io_res == NULL || sm->mem_res == NULL) { dev_err(&dev->dev, "failed to get IO resource\n"); err = -ENOENT; goto err_res; } sm->regs_claim = request_mem_region(sm->io_res->start, 0x100, "sm501"); if (sm->regs_claim == NULL) { dev_err(&dev->dev, "cannot claim registers\n"); err= -EBUSY; goto err_res; } platform_set_drvdata(dev, sm); sm->regs = ioremap(sm->io_res->start, (sm->io_res->end - sm->io_res->start) - 1); if (sm->regs == NULL) { dev_err(&dev->dev, "cannot remap registers\n"); err = -EIO; goto err_claim; } return sm501_init_dev(sm); err_claim: release_resource(sm->regs_claim); kfree(sm->regs_claim); err_res: kfree(sm); err1: return err; } /* Initialisation data for PCI devices */ static struct sm501_initdata sm501_pci_initdata = { .gpio_high = { .set = 0x3F000000, /* 24bit panel */ .mask = 0x0, }, .misc_timing = { .set = 0x010100, /* SDRAM timing */ .mask = 0x1F1F00, }, .misc_control = { .set = SM501_MISC_PNL_24BIT, .mask = 0, }, .devices = SM501_USE_ALL, .mclk = 100 * MHZ, .m1xclk = 160 * MHZ, }; static struct sm501_platdata_fbsub sm501_pdata_fbsub = { .flags = (SM501FB_FLAG_USE_INIT_MODE | SM501FB_FLAG_USE_HWCURSOR | SM501FB_FLAG_USE_HWACCEL | SM501FB_FLAG_DISABLE_AT_EXIT), }; static struct sm501_platdata_fb sm501_fb_pdata = { .fb_route = SM501_FB_OWN, .fb_crt = &sm501_pdata_fbsub, .fb_pnl = &sm501_pdata_fbsub, }; static struct sm501_platdata sm501_pci_platdata = { .init = &sm501_pci_initdata, .fb = &sm501_fb_pdata, }; static int sm501_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { struct sm501_devdata *sm; int err; sm = kzalloc(sizeof(struct sm501_devdata), GFP_KERNEL); if (sm == NULL) { dev_err(&dev->dev, "no memory for device data\n"); err = -ENOMEM; goto err1; } /* set a default set of platform data */ dev->dev.platform_data = sm->platdata = &sm501_pci_platdata; /* set a hopefully unique id for our child platform devices */ sm->pdev_id = 32 + dev->devfn; pci_set_drvdata(dev, sm); err = pci_enable_device(dev); if (err) { dev_err(&dev->dev, "cannot enable device\n"); goto err2; } sm->dev = &dev->dev; sm->irq = dev->irq; #ifdef __BIG_ENDIAN /* if the system is big-endian, we most probably have a * translation in the IO layer making the PCI bus little endian * so make the framebuffer swapped pixels */ sm501_fb_pdata.flags |= SM501_FBPD_SWAP_FB_ENDIAN; #endif /* check our resources */ if (!(pci_resource_flags(dev, 0) & IORESOURCE_MEM)) { dev_err(&dev->dev, "region #0 is not memory?\n"); err = -EINVAL; goto err3; } if (!(pci_resource_flags(dev, 1) & IORESOURCE_MEM)) { dev_err(&dev->dev, "region #1 is not memory?\n"); err = -EINVAL; goto err3; } /* make our resources ready for sharing */ sm->io_res = &dev->resource[1]; sm->mem_res = &dev->resource[0]; sm->regs_claim = request_mem_region(sm->io_res->start, 0x100, "sm501"); if (sm->regs_claim == NULL) { dev_err(&dev->dev, "cannot claim registers\n"); err= -EBUSY; goto err3; } sm->regs = ioremap(pci_resource_start(dev, 1), pci_resource_len(dev, 1)); if (sm->regs == NULL) { dev_err(&dev->dev, "cannot remap registers\n"); err = -EIO; goto err4; } sm501_init_dev(sm); return 0; err4: release_resource(sm->regs_claim); kfree(sm->regs_claim); err3: pci_disable_device(dev); err2: pci_set_drvdata(dev, NULL); kfree(sm); err1: return err; } static void sm501_remove_sub(struct sm501_devdata *sm, struct sm501_device *smdev) { list_del(&smdev->list); platform_device_unregister(&smdev->pdev); } static void sm501_dev_remove(struct sm501_devdata *sm) { struct sm501_device *smdev, *tmp; list_for_each_entry_safe(smdev, tmp, &sm->devices, list) sm501_remove_sub(sm, smdev); device_remove_file(sm->dev, &dev_attr_dbg_regs); } static void sm501_pci_remove(struct pci_dev *dev) { struct sm501_devdata *sm = pci_get_drvdata(dev); sm501_dev_remove(sm); iounmap(sm->regs); release_resource(sm->regs_claim); kfree(sm->regs_claim); pci_set_drvdata(dev, NULL); pci_disable_device(dev); } static int sm501_plat_remove(struct platform_device *dev) { struct sm501_devdata *sm = platform_get_drvdata(dev); sm501_dev_remove(sm); iounmap(sm->regs); release_resource(sm->regs_claim); kfree(sm->regs_claim); return 0; } static struct pci_device_id sm501_pci_tbl[] = { { 0x126f, 0x0501, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, { 0, }, }; MODULE_DEVICE_TABLE(pci, sm501_pci_tbl); static struct pci_driver sm501_pci_drv = { .name = "sm501", .id_table = sm501_pci_tbl, .probe = sm501_pci_probe, .remove = sm501_pci_remove, }; static struct platform_driver sm501_plat_drv = { .driver = { .name = "sm501", .owner = THIS_MODULE, }, .probe = sm501_plat_probe, .remove = sm501_plat_remove, }; static int __init sm501_base_init(void) { platform_driver_register(&sm501_plat_drv); return pci_register_driver(&sm501_pci_drv); } static void __exit sm501_base_exit(void) { platform_driver_unregister(&sm501_plat_drv); pci_unregister_driver(&sm501_pci_drv); } module_init(sm501_base_init); module_exit(sm501_base_exit); MODULE_DESCRIPTION("SM501 Core Driver"); MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, Vincent Sanders"); MODULE_LICENSE("GPL v2");