|  | /* 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"); |