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 + -
显示快捷键?