sm501.c
来自「linux 内核源代码」· C语言 代码 · 共 1,284 行 · 第 1/2 页
C
1,284 行
/* 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; unsigned int in_suspend; unsigned long pm_misc; int unit_power[20]; unsigned int pdev_id; unsigned int irq; void __iomem *regs;};#define MHZ (1000 * 1000)#ifdef DEBUGstatic 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), " "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)));}static void sm501_dump_regs(struct sm501_devdata *sm){ void __iomem *regs = sm->regs; dev_info(sm->dev, "System Control %08x\n", readl(regs + SM501_SYSTEM_CONTROL)); dev_info(sm->dev, "Misc Control %08x\n", readl(regs + SM501_MISC_CONTROL)); dev_info(sm->dev, "GPIO Control Low %08x\n", readl(regs + SM501_GPIO31_0_CONTROL)); dev_info(sm->dev, "GPIO Control Hi %08x\n", readl(regs + SM501_GPIO63_32_CONTROL)); dev_info(sm->dev, "DRAM Control %08x\n", readl(regs + SM501_DRAM_CONTROL)); dev_info(sm->dev, "Arbitration Ctrl %08x\n", readl(regs + SM501_ARBTRTN_CONTROL)); dev_info(sm->dev, "Misc Timing %08x\n", readl(regs + SM501_MISC_TIMING));}static void sm501_dump_gate(struct sm501_devdata *sm){ dev_info(sm->dev, "CurrentGate %08x\n", readl(sm->regs + SM501_CURRENT_GATE)); dev_info(sm->dev, "CurrentClock %08x\n", readl(sm->regs + SM501_CURRENT_CLOCK)); dev_info(sm->dev, "PowerModeControl %08x\n", readl(sm->regs + SM501_POWER_MODE_CONTROL));}#elsestatic inline void sm501_dump_gate(struct sm501_devdata *sm) { }static inline void sm501_dump_regs(struct sm501_devdata *sm) { }static inline 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);}static inline void sm501_mdelay(struct sm501_devdata *sm, unsigned int delay){ /* during suspend/resume, we are currently not allowed to sleep, * so change to using mdelay() instead of msleep() if we * are in one of these paths */ if (sm->in_suspend) mdelay(delay); else msleep(delay);}/* sm501_misc_control * * alters the miscellaneous 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); sm501_mdelay(sm, 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); sm501_mdelay(sm, 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)
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?