2007-02-20 22:58:01 +01:00
|
|
|
/* 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>
|
2008-07-25 10:46:01 +02:00
|
|
|
#include <linux/i2c-gpio.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 09:04:11 +01:00
|
|
|
#include <linux/slab.h>
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
#include <linux/sm501.h>
|
|
|
|
#include <linux/sm501-regs.h>
|
2008-04-28 11:14:22 +02:00
|
|
|
#include <linux/serial_8250.h>
|
2007-02-20 22:58:01 +01:00
|
|
|
|
2010-11-10 08:47:51 +01:00
|
|
|
#include <linux/io.h>
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
struct sm501_device {
|
|
|
|
struct list_head list;
|
|
|
|
struct platform_device pdev;
|
|
|
|
};
|
|
|
|
|
2008-07-25 10:45:59 +02:00
|
|
|
struct sm501_gpio;
|
|
|
|
|
2008-07-25 10:46:02 +02:00
|
|
|
#ifdef CONFIG_MFD_SM501_GPIO
|
|
|
|
#include <linux/gpio.h>
|
|
|
|
|
2008-07-25 10:45:59 +02:00
|
|
|
struct sm501_gpio_chip {
|
|
|
|
struct gpio_chip gpio;
|
|
|
|
struct sm501_gpio *ourgpio; /* to get back to parent. */
|
|
|
|
void __iomem *regbase;
|
2009-01-10 16:59:53 +01:00
|
|
|
void __iomem *control; /* address of control reg. */
|
2008-07-25 10:45:59 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
struct sm501_gpio {
|
|
|
|
struct sm501_gpio_chip low;
|
|
|
|
struct sm501_gpio_chip high;
|
|
|
|
spinlock_t lock;
|
|
|
|
|
|
|
|
unsigned int registered : 1;
|
|
|
|
void __iomem *regs;
|
|
|
|
struct resource *regs_res;
|
|
|
|
};
|
2008-07-25 10:46:02 +02:00
|
|
|
#else
|
|
|
|
struct sm501_gpio {
|
|
|
|
/* no gpio support, empty definition for sm501_devdata. */
|
|
|
|
};
|
|
|
|
#endif
|
2008-07-25 10:45:59 +02:00
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
struct sm501_devdata {
|
|
|
|
spinlock_t reg_lock;
|
|
|
|
struct mutex clock_lock;
|
|
|
|
struct list_head devices;
|
2008-07-25 10:45:59 +02:00
|
|
|
struct sm501_gpio gpio;
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
struct device *dev;
|
|
|
|
struct resource *io_res;
|
|
|
|
struct resource *mem_res;
|
|
|
|
struct resource *regs_claim;
|
|
|
|
struct sm501_platdata *platdata;
|
|
|
|
|
2008-07-25 10:45:59 +02:00
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
unsigned int in_suspend;
|
|
|
|
unsigned long pm_misc;
|
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
int unit_power[20];
|
|
|
|
unsigned int pdev_id;
|
|
|
|
unsigned int irq;
|
|
|
|
void __iomem *regs;
|
2008-03-04 23:28:50 +01:00
|
|
|
unsigned int rev;
|
2007-02-20 22:58:01 +01:00
|
|
|
};
|
|
|
|
|
2008-07-25 10:45:59 +02:00
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
#define MHZ (1000 * 1000)
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
2008-03-04 23:28:49 +01:00
|
|
|
static const unsigned int div_tab[] = {
|
2007-02-20 22:58:01 +01:00
|
|
|
[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,
|
2008-03-04 23:28:49 +01:00
|
|
|
unsigned long mask)
|
2007-02-20 22:58:01 +01:00
|
|
|
{
|
|
|
|
if (val & selbit)
|
|
|
|
pll2 = 288 * MHZ;
|
|
|
|
|
2008-03-04 23:28:49 +01:00
|
|
|
return pll2 / div_tab[(val >> lshft) & mask];
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#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;
|
2008-03-04 23:28:49 +01:00
|
|
|
sdclk0 /= div_tab[((misct >> 8) & 0xf)];
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
sdclk1 = (misct & (1<<20)) ? pll2 : 288 * MHZ;
|
2008-03-04 23:28:49 +01:00
|
|
|
sdclk1 /= div_tab[((misct >> 16) & 0xf)];
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
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), "
|
2007-11-29 01:21:37 +01:00
|
|
|
"M %ld.%ld (%ld), MX1 %ld.%ld (%ld)\n",
|
2007-02-20 22:58:01 +01:00
|
|
|
(pmc & 3 ) == 0 ? '*' : '-',
|
2008-03-04 23:28:49 +01:00
|
|
|
fmt_freq(decode_div(pll2, pm0, 24, 1<<29, 31)),
|
|
|
|
fmt_freq(decode_div(pll2, pm0, 16, 1<<20, 15)),
|
|
|
|
fmt_freq(decode_div(pll2, pm0, 8, 1<<12, 15)),
|
|
|
|
fmt_freq(decode_div(pll2, pm0, 0, 1<<4, 15)));
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
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 ? '*' : '-',
|
2008-03-04 23:28:49 +01:00
|
|
|
fmt_freq(decode_div(pll2, pm1, 24, 1<<29, 31)),
|
|
|
|
fmt_freq(decode_div(pll2, pm1, 16, 1<<20, 15)),
|
|
|
|
fmt_freq(decode_div(pll2, pm1, 8, 1<<12, 15)),
|
|
|
|
fmt_freq(decode_div(pll2, pm1, 0, 1<<4, 15)));
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
2007-06-24 02:16:28 +02:00
|
|
|
|
|
|
|
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)
|
2007-02-20 22:58:01 +01:00
|
|
|
{
|
2007-06-24 02:16:28 +02:00
|
|
|
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));
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
2007-06-24 02:16:28 +02:00
|
|
|
|
|
|
|
#else
|
|
|
|
static 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) { }
|
2007-02-20 22:58:01 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* sm501_sync_regs
|
|
|
|
*
|
|
|
|
* ensure the
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void sm501_sync_regs(struct sm501_devdata *sm)
|
|
|
|
{
|
|
|
|
readl(sm->regs);
|
|
|
|
}
|
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
/* sm501_misc_control
|
|
|
|
*
|
2007-06-24 02:16:28 +02:00
|
|
|
* alters the miscellaneous control parameters
|
2007-02-20 22:58:01 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
|
2007-04-02 08:49:38 +02:00
|
|
|
if (unit >= ARRAY_SIZE(sm->unit_power)) {
|
2008-04-30 09:54:57 +02:00
|
|
|
dev_err(dev, "%s: bad unit %d\n", __func__, unit);
|
2007-02-20 22:58:01 +01:00
|
|
|
goto already;
|
|
|
|
}
|
|
|
|
|
2008-04-30 09:54:57 +02:00
|
|
|
dev_dbg(sm->dev, "%s: unit %d, cur %d, to %d\n", __func__, unit,
|
2007-02-20 22:58:01 +01:00
|
|
|
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:
|
2009-06-22 00:00:02 +02:00
|
|
|
gate = -1;
|
|
|
|
goto already;
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
sm501_mdelay(sm, 16);
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
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;
|
2008-03-04 23:28:50 +01:00
|
|
|
unsigned int m, n, k;
|
2007-02-20 22:58:01 +01:00
|
|
|
};
|
|
|
|
|
2008-03-04 23:28:50 +01:00
|
|
|
/* sm501_calc_clock
|
|
|
|
*
|
|
|
|
* Calculates the nearest discrete clock frequency that
|
|
|
|
* can be achieved with the specified input clock.
|
|
|
|
* the maximum divisor is 3 or 5
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int sm501_calc_clock(unsigned long freq,
|
|
|
|
struct sm501_clock *clock,
|
|
|
|
int max_div,
|
|
|
|
unsigned long mclk,
|
|
|
|
long *best_diff)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
int divider;
|
|
|
|
int shift;
|
|
|
|
long diff;
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sm501_calc_pll
|
|
|
|
*
|
|
|
|
* Calculates the nearest discrete clock frequency that can be
|
|
|
|
* achieved using the programmable PLL.
|
|
|
|
* the maximum divisor is 3 or 5
|
|
|
|
*/
|
|
|
|
|
|
|
|
static unsigned long sm501_calc_pll(unsigned long freq,
|
|
|
|
struct sm501_clock *clock,
|
|
|
|
int max_div)
|
|
|
|
{
|
|
|
|
unsigned long mclk;
|
|
|
|
unsigned int m, n, k;
|
|
|
|
long best_diff = 999999999;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The SM502 datasheet doesn't specify the min/max values for M and N.
|
|
|
|
* N = 1 at least doesn't work in practice.
|
|
|
|
*/
|
|
|
|
for (m = 2; m <= 255; m++) {
|
|
|
|
for (n = 2; n <= 127; n++) {
|
|
|
|
for (k = 0; k <= 1; k++) {
|
|
|
|
mclk = (24000000UL * m / n) >> k;
|
|
|
|
|
|
|
|
if (sm501_calc_clock(freq, clock, max_div,
|
|
|
|
mclk, &best_diff)) {
|
|
|
|
clock->m = m;
|
|
|
|
clock->n = n;
|
|
|
|
clock->k = k;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return best clock. */
|
|
|
|
return clock->mclk / (clock->divider << clock->shift);
|
|
|
|
}
|
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
/* sm501_select_clock
|
|
|
|
*
|
2008-03-04 23:28:50 +01:00
|
|
|
* Calculates the nearest discrete clock frequency that can be
|
|
|
|
* achieved using the 288MHz and 336MHz PLLs.
|
2007-02-20 22:58:01 +01:00
|
|
|
* the maximum divisor is 3 or 5
|
|
|
|
*/
|
2008-03-04 23:28:50 +01:00
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
static unsigned long sm501_select_clock(unsigned long freq,
|
|
|
|
struct sm501_clock *clock,
|
|
|
|
int max_div)
|
|
|
|
{
|
|
|
|
unsigned long mclk;
|
|
|
|
long best_diff = 999999999;
|
|
|
|
|
|
|
|
/* Try 288MHz and 336MHz clocks. */
|
|
|
|
for (mclk = 288000000; mclk <= 336000000; mclk += 48000000) {
|
2008-03-04 23:28:50 +01:00
|
|
|
sm501_calc_clock(freq, clock, max_div, mclk, &best_diff);
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
2008-03-04 23:28:50 +01:00
|
|
|
unsigned int pll_reg = 0;
|
tree-wide: Assorted spelling fixes
In particular, several occurances of funny versions of 'success',
'unknown', 'therefore', 'acknowledge', 'argument', 'achieve', 'address',
'beginning', 'desirable', 'separate' and 'necessary' are fixed.
Signed-off-by: Daniel Mack <daniel@caiaq.de>
Cc: Joe Perches <joe@perches.com>
Cc: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-02-03 01:01:28 +01:00
|
|
|
unsigned long sm501_freq; /* the actual frequency achieved */
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
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:
|
tree-wide: Assorted spelling fixes
In particular, several occurances of funny versions of 'success',
'unknown', 'therefore', 'acknowledge', 'argument', 'achieve', 'address',
'beginning', 'desirable', 'separate' and 'necessary' are fixed.
Signed-off-by: Daniel Mack <daniel@caiaq.de>
Cc: Joe Perches <joe@perches.com>
Cc: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-02-03 01:01:28 +01:00
|
|
|
/* This clock is divided in half so to achieve the
|
2007-02-20 22:58:01 +01:00
|
|
|
* requested frequency the value must be multiplied by
|
|
|
|
* 2. This clock also has an additional pre divisor */
|
|
|
|
|
2008-03-04 23:28:50 +01:00
|
|
|
if (sm->rev >= 0xC0) {
|
|
|
|
/* SM502 -> use the programmable PLL */
|
|
|
|
sm501_freq = (sm501_calc_pll(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 */
|
|
|
|
reg |= 0x40; /* select the programmable PLL */
|
|
|
|
pll_reg = 0x20000 | (to.k << 15) | (to.n << 8) | to.m;
|
|
|
|
} else {
|
|
|
|
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 */
|
|
|
|
}
|
2007-02-20 22:58:01 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SM501_CLOCK_V2XCLK:
|
tree-wide: Assorted spelling fixes
In particular, several occurances of funny versions of 'success',
'unknown', 'therefore', 'acknowledge', 'argument', 'achieve', 'address',
'beginning', 'desirable', 'separate' and 'necessary' are fixed.
Signed-off-by: Daniel Mack <daniel@caiaq.de>
Cc: Joe Perches <joe@perches.com>
Cc: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-02-03 01:01:28 +01:00
|
|
|
/* This clock is divided in half so to achieve the
|
2007-02-20 22:58:01 +01:00
|
|
|
* 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);
|
2008-03-04 23:28:50 +01:00
|
|
|
|
|
|
|
if (pll_reg)
|
|
|
|
writel(pll_reg, sm->regs + SM501_PROGRAMMABLE_PLL_CONTROL);
|
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
sm501_sync_regs(sm);
|
|
|
|
|
2008-08-10 23:45:24 +02:00
|
|
|
dev_dbg(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n",
|
|
|
|
gate, clock, mode);
|
2007-02-20 22:58:01 +01:00
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
sm501_mdelay(sm, 16);
|
2007-02-20 22:58:01 +01:00
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2008-03-04 23:28:50 +01:00
|
|
|
unsigned long sm501_find_clock(struct device *dev,
|
|
|
|
int clksrc,
|
2007-02-20 22:58:01 +01:00
|
|
|
unsigned long req_freq)
|
|
|
|
{
|
2008-03-04 23:28:50 +01:00
|
|
|
struct sm501_devdata *sm = dev_get_drvdata(dev);
|
tree-wide: Assorted spelling fixes
In particular, several occurances of funny versions of 'success',
'unknown', 'therefore', 'acknowledge', 'argument', 'achieve', 'address',
'beginning', 'desirable', 'separate' and 'necessary' are fixed.
Signed-off-by: Daniel Mack <daniel@caiaq.de>
Cc: Joe Perches <joe@perches.com>
Cc: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-02-03 01:01:28 +01:00
|
|
|
unsigned long sm501_freq; /* the frequency achieveable by the 501 */
|
2007-02-20 22:58:01 +01:00
|
|
|
struct sm501_clock to;
|
|
|
|
|
|
|
|
switch (clksrc) {
|
|
|
|
case SM501_CLOCK_P2XCLK:
|
2008-03-04 23:28:50 +01:00
|
|
|
if (sm->rev >= 0xC0) {
|
|
|
|
/* SM502 -> use the programmable PLL */
|
|
|
|
sm501_freq = (sm501_calc_pll(2 * req_freq,
|
|
|
|
&to, 5) / 2);
|
|
|
|
} else {
|
|
|
|
sm501_freq = (sm501_select_clock(2 * req_freq,
|
|
|
|
&to, 5) / 2);
|
|
|
|
}
|
2007-02-20 22:58:01 +01:00
|
|
|
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 *
|
2008-04-28 11:14:22 +02:00
|
|
|
sm501_create_subdev(struct sm501_devdata *sm, char *name,
|
|
|
|
unsigned int res_count, unsigned int platform_data_size)
|
2007-02-20 22:58:01 +01:00
|
|
|
{
|
|
|
|
struct sm501_device *smdev;
|
|
|
|
|
|
|
|
smdev = kzalloc(sizeof(struct sm501_device) +
|
2008-04-28 11:14:22 +02:00
|
|
|
(sizeof(struct resource) * res_count) +
|
|
|
|
platform_data_size, GFP_KERNEL);
|
2007-02-20 22:58:01 +01:00
|
|
|
if (!smdev)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
smdev->pdev.dev.release = sm501_device_release;
|
|
|
|
|
|
|
|
smdev->pdev.name = name;
|
|
|
|
smdev->pdev.id = sm->pdev_id;
|
|
|
|
smdev->pdev.dev.parent = sm->dev;
|
|
|
|
|
2008-04-28 11:14:22 +02:00
|
|
|
if (res_count) {
|
|
|
|
smdev->pdev.resource = (struct resource *)(smdev+1);
|
|
|
|
smdev->pdev.num_resources = res_count;
|
|
|
|
}
|
|
|
|
if (platform_data_size)
|
|
|
|
smdev->pdev.dev.platform_data = (void *)(smdev+1);
|
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
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++) {
|
2010-11-12 22:37:56 +01:00
|
|
|
printk(KERN_DEBUG "%s[%d] %pR\n",
|
|
|
|
pdev->name, ptr, &pdev->resource[ptr]);
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2008-04-28 11:14:22 +02:00
|
|
|
pdev = sm501_create_subdev(sm, "sm501-usb", 3, 0);
|
2007-02-20 22:58:01 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2008-04-28 11:14:22 +02:00
|
|
|
static void sm501_setup_uart_data(struct sm501_devdata *sm,
|
|
|
|
struct plat_serial8250_port *uart_data,
|
|
|
|
unsigned int offset)
|
|
|
|
{
|
|
|
|
uart_data->membase = sm->regs + offset;
|
|
|
|
uart_data->mapbase = sm->io_res->start + offset;
|
|
|
|
uart_data->iotype = UPIO_MEM;
|
|
|
|
uart_data->irq = sm->irq;
|
|
|
|
uart_data->flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | UPF_SHARE_IRQ;
|
|
|
|
uart_data->regshift = 2;
|
|
|
|
uart_data->uartclk = (9600 * 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sm501_register_uart(struct sm501_devdata *sm, int devices)
|
|
|
|
{
|
|
|
|
struct platform_device *pdev;
|
|
|
|
struct plat_serial8250_port *uart_data;
|
|
|
|
|
|
|
|
pdev = sm501_create_subdev(sm, "serial8250", 0,
|
|
|
|
sizeof(struct plat_serial8250_port) * 3);
|
|
|
|
if (!pdev)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
uart_data = pdev->dev.platform_data;
|
|
|
|
|
|
|
|
if (devices & SM501_USE_UART0) {
|
|
|
|
sm501_setup_uart_data(sm, uart_data++, 0x30000);
|
|
|
|
sm501_unit_power(sm->dev, SM501_GATE_UART0, 1);
|
|
|
|
sm501_modify_reg(sm->dev, SM501_IRQ_MASK, 1 << 12, 0);
|
|
|
|
sm501_modify_reg(sm->dev, SM501_GPIO63_32_CONTROL, 0x01e0, 0);
|
|
|
|
}
|
|
|
|
if (devices & SM501_USE_UART1) {
|
|
|
|
sm501_setup_uart_data(sm, uart_data++, 0x30020);
|
|
|
|
sm501_unit_power(sm->dev, SM501_GATE_UART1, 1);
|
|
|
|
sm501_modify_reg(sm->dev, SM501_IRQ_MASK, 1 << 13, 0);
|
|
|
|
sm501_modify_reg(sm->dev, SM501_GPIO63_32_CONTROL, 0x1e00, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
pdev->id = PLAT8250_DEV_SM501;
|
|
|
|
|
|
|
|
return sm501_register_device(sm, pdev);
|
|
|
|
}
|
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
static int sm501_register_display(struct sm501_devdata *sm,
|
|
|
|
resource_size_t *mem_avail)
|
|
|
|
{
|
|
|
|
struct platform_device *pdev;
|
|
|
|
|
2008-04-28 11:14:22 +02:00
|
|
|
pdev = sm501_create_subdev(sm, "sm501-fb", 4, 0);
|
2007-02-20 22:58:01 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2008-07-25 10:45:59 +02:00
|
|
|
#ifdef CONFIG_MFD_SM501_GPIO
|
|
|
|
|
|
|
|
static inline struct sm501_gpio_chip *to_sm501_gpio(struct gpio_chip *gc)
|
|
|
|
{
|
|
|
|
return container_of(gc, struct sm501_gpio_chip, gpio);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline struct sm501_devdata *sm501_gpio_to_dev(struct sm501_gpio *gpio)
|
|
|
|
{
|
|
|
|
return container_of(gpio, struct sm501_devdata, gpio);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sm501_gpio_get(struct gpio_chip *chip, unsigned offset)
|
|
|
|
|
|
|
|
{
|
|
|
|
struct sm501_gpio_chip *smgpio = to_sm501_gpio(chip);
|
|
|
|
unsigned long result;
|
|
|
|
|
|
|
|
result = readl(smgpio->regbase + SM501_GPIO_DATA_LOW);
|
|
|
|
result >>= offset;
|
|
|
|
|
|
|
|
return result & 1UL;
|
|
|
|
}
|
|
|
|
|
2009-01-10 16:59:53 +01:00
|
|
|
static void sm501_gpio_ensure_gpio(struct sm501_gpio_chip *smchip,
|
|
|
|
unsigned long bit)
|
|
|
|
{
|
|
|
|
unsigned long ctrl;
|
|
|
|
|
|
|
|
/* check and modify if this pin is not set as gpio. */
|
|
|
|
|
|
|
|
if (readl(smchip->control) & bit) {
|
|
|
|
dev_info(sm501_gpio_to_dev(smchip->ourgpio)->dev,
|
|
|
|
"changing mode of gpio, bit %08lx\n", bit);
|
|
|
|
|
|
|
|
ctrl = readl(smchip->control);
|
|
|
|
ctrl &= ~bit;
|
|
|
|
writel(ctrl, smchip->control);
|
|
|
|
|
|
|
|
sm501_sync_regs(sm501_gpio_to_dev(smchip->ourgpio));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-07-25 10:45:59 +02:00
|
|
|
static void sm501_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
|
|
|
|
|
|
|
{
|
|
|
|
struct sm501_gpio_chip *smchip = to_sm501_gpio(chip);
|
|
|
|
struct sm501_gpio *smgpio = smchip->ourgpio;
|
|
|
|
unsigned long bit = 1 << offset;
|
|
|
|
void __iomem *regs = smchip->regbase;
|
|
|
|
unsigned long save;
|
|
|
|
unsigned long val;
|
|
|
|
|
|
|
|
dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d)\n",
|
|
|
|
__func__, chip, offset);
|
|
|
|
|
|
|
|
spin_lock_irqsave(&smgpio->lock, save);
|
|
|
|
|
|
|
|
val = readl(regs + SM501_GPIO_DATA_LOW) & ~bit;
|
|
|
|
if (value)
|
|
|
|
val |= bit;
|
|
|
|
writel(val, regs);
|
|
|
|
|
|
|
|
sm501_sync_regs(sm501_gpio_to_dev(smgpio));
|
2009-01-10 16:59:53 +01:00
|
|
|
sm501_gpio_ensure_gpio(smchip, bit);
|
|
|
|
|
2008-07-25 10:45:59 +02:00
|
|
|
spin_unlock_irqrestore(&smgpio->lock, save);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sm501_gpio_input(struct gpio_chip *chip, unsigned offset)
|
|
|
|
{
|
|
|
|
struct sm501_gpio_chip *smchip = to_sm501_gpio(chip);
|
|
|
|
struct sm501_gpio *smgpio = smchip->ourgpio;
|
|
|
|
void __iomem *regs = smchip->regbase;
|
|
|
|
unsigned long bit = 1 << offset;
|
|
|
|
unsigned long save;
|
|
|
|
unsigned long ddr;
|
|
|
|
|
2009-01-10 16:59:53 +01:00
|
|
|
dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d)\n",
|
|
|
|
__func__, chip, offset);
|
2008-07-25 10:45:59 +02:00
|
|
|
|
|
|
|
spin_lock_irqsave(&smgpio->lock, save);
|
|
|
|
|
|
|
|
ddr = readl(regs + SM501_GPIO_DDR_LOW);
|
|
|
|
writel(ddr & ~bit, regs + SM501_GPIO_DDR_LOW);
|
|
|
|
|
|
|
|
sm501_sync_regs(sm501_gpio_to_dev(smgpio));
|
2009-01-10 16:59:53 +01:00
|
|
|
sm501_gpio_ensure_gpio(smchip, bit);
|
|
|
|
|
2008-07-25 10:45:59 +02:00
|
|
|
spin_unlock_irqrestore(&smgpio->lock, save);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sm501_gpio_output(struct gpio_chip *chip,
|
|
|
|
unsigned offset, int value)
|
|
|
|
{
|
|
|
|
struct sm501_gpio_chip *smchip = to_sm501_gpio(chip);
|
|
|
|
struct sm501_gpio *smgpio = smchip->ourgpio;
|
|
|
|
unsigned long bit = 1 << offset;
|
|
|
|
void __iomem *regs = smchip->regbase;
|
|
|
|
unsigned long save;
|
|
|
|
unsigned long val;
|
|
|
|
unsigned long ddr;
|
|
|
|
|
|
|
|
dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d,%d)\n",
|
|
|
|
__func__, chip, offset, value);
|
|
|
|
|
|
|
|
spin_lock_irqsave(&smgpio->lock, save);
|
|
|
|
|
|
|
|
val = readl(regs + SM501_GPIO_DATA_LOW);
|
|
|
|
if (value)
|
|
|
|
val |= bit;
|
|
|
|
else
|
|
|
|
val &= ~bit;
|
|
|
|
writel(val, regs);
|
|
|
|
|
|
|
|
ddr = readl(regs + SM501_GPIO_DDR_LOW);
|
|
|
|
writel(ddr | bit, regs + SM501_GPIO_DDR_LOW);
|
|
|
|
|
|
|
|
sm501_sync_regs(sm501_gpio_to_dev(smgpio));
|
|
|
|
writel(val, regs + SM501_GPIO_DATA_LOW);
|
|
|
|
|
|
|
|
sm501_sync_regs(sm501_gpio_to_dev(smgpio));
|
|
|
|
spin_unlock_irqrestore(&smgpio->lock, save);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct gpio_chip gpio_chip_template = {
|
|
|
|
.ngpio = 32,
|
|
|
|
.direction_input = sm501_gpio_input,
|
|
|
|
.direction_output = sm501_gpio_output,
|
|
|
|
.set = sm501_gpio_set,
|
|
|
|
.get = sm501_gpio_get,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __devinit sm501_gpio_register_chip(struct sm501_devdata *sm,
|
|
|
|
struct sm501_gpio *gpio,
|
|
|
|
struct sm501_gpio_chip *chip)
|
|
|
|
{
|
|
|
|
struct sm501_platdata *pdata = sm->platdata;
|
|
|
|
struct gpio_chip *gchip = &chip->gpio;
|
2008-07-25 10:46:00 +02:00
|
|
|
int base = pdata->gpio_base;
|
2008-07-25 10:45:59 +02:00
|
|
|
|
2008-07-25 10:46:02 +02:00
|
|
|
chip->gpio = gpio_chip_template;
|
2008-07-25 10:45:59 +02:00
|
|
|
|
|
|
|
if (chip == &gpio->high) {
|
2008-07-25 10:46:00 +02:00
|
|
|
if (base > 0)
|
|
|
|
base += 32;
|
2008-07-25 10:45:59 +02:00
|
|
|
chip->regbase = gpio->regs + SM501_GPIO_DATA_HIGH;
|
2009-01-10 16:59:53 +01:00
|
|
|
chip->control = sm->regs + SM501_GPIO63_32_CONTROL;
|
2008-07-25 10:45:59 +02:00
|
|
|
gchip->label = "SM501-HIGH";
|
|
|
|
} else {
|
|
|
|
chip->regbase = gpio->regs + SM501_GPIO_DATA_LOW;
|
2009-01-10 16:59:53 +01:00
|
|
|
chip->control = sm->regs + SM501_GPIO31_0_CONTROL;
|
2008-07-25 10:45:59 +02:00
|
|
|
gchip->label = "SM501-LOW";
|
|
|
|
}
|
|
|
|
|
|
|
|
gchip->base = base;
|
|
|
|
chip->ourgpio = gpio;
|
|
|
|
|
|
|
|
return gpiochip_add(gchip);
|
|
|
|
}
|
|
|
|
|
2009-02-17 09:21:52 +01:00
|
|
|
static int __devinit sm501_register_gpio(struct sm501_devdata *sm)
|
2008-07-25 10:45:59 +02:00
|
|
|
{
|
|
|
|
struct sm501_gpio *gpio = &sm->gpio;
|
|
|
|
resource_size_t iobase = sm->io_res->start + SM501_GPIO;
|
|
|
|
int ret;
|
|
|
|
int tmp;
|
|
|
|
|
|
|
|
dev_dbg(sm->dev, "registering gpio block %08llx\n",
|
|
|
|
(unsigned long long)iobase);
|
|
|
|
|
|
|
|
spin_lock_init(&gpio->lock);
|
|
|
|
|
|
|
|
gpio->regs_res = request_mem_region(iobase, 0x20, "sm501-gpio");
|
|
|
|
if (gpio->regs_res == NULL) {
|
|
|
|
dev_err(sm->dev, "gpio: failed to request region\n");
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
gpio->regs = ioremap(iobase, 0x20);
|
|
|
|
if (gpio->regs == NULL) {
|
|
|
|
dev_err(sm->dev, "gpio: failed to remap registers\n");
|
|
|
|
ret = -ENXIO;
|
2008-07-25 10:46:02 +02:00
|
|
|
goto err_claimed;
|
2008-07-25 10:45:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Register both our chips. */
|
|
|
|
|
|
|
|
ret = sm501_gpio_register_chip(sm, gpio, &gpio->low);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(sm->dev, "failed to add low chip\n");
|
|
|
|
goto err_mapped;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sm501_gpio_register_chip(sm, gpio, &gpio->high);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(sm->dev, "failed to add high chip\n");
|
|
|
|
goto err_low_chip;
|
|
|
|
}
|
|
|
|
|
|
|
|
gpio->registered = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_low_chip:
|
|
|
|
tmp = gpiochip_remove(&gpio->low.gpio);
|
|
|
|
if (tmp) {
|
|
|
|
dev_err(sm->dev, "cannot remove low chip, cannot tidy up\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
err_mapped:
|
2008-07-25 10:46:02 +02:00
|
|
|
iounmap(gpio->regs);
|
|
|
|
|
|
|
|
err_claimed:
|
2008-07-25 10:45:59 +02:00
|
|
|
release_resource(gpio->regs_res);
|
|
|
|
kfree(gpio->regs_res);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sm501_gpio_remove(struct sm501_devdata *sm)
|
|
|
|
{
|
2008-07-25 10:46:02 +02:00
|
|
|
struct sm501_gpio *gpio = &sm->gpio;
|
2008-07-25 10:45:59 +02:00
|
|
|
int ret;
|
|
|
|
|
2008-07-25 10:46:02 +02:00
|
|
|
if (!sm->gpio.registered)
|
|
|
|
return;
|
|
|
|
|
2008-07-25 10:46:02 +02:00
|
|
|
ret = gpiochip_remove(&gpio->low.gpio);
|
2008-07-25 10:45:59 +02:00
|
|
|
if (ret)
|
|
|
|
dev_err(sm->dev, "cannot remove low chip, cannot tidy up\n");
|
|
|
|
|
2008-07-25 10:46:02 +02:00
|
|
|
ret = gpiochip_remove(&gpio->high.gpio);
|
2008-07-25 10:45:59 +02:00
|
|
|
if (ret)
|
|
|
|
dev_err(sm->dev, "cannot remove high chip, cannot tidy up\n");
|
2008-07-25 10:46:02 +02:00
|
|
|
|
|
|
|
iounmap(gpio->regs);
|
|
|
|
release_resource(gpio->regs_res);
|
|
|
|
kfree(gpio->regs_res);
|
2008-07-25 10:45:59 +02:00
|
|
|
}
|
|
|
|
|
2008-07-25 10:46:02 +02:00
|
|
|
static inline int sm501_gpio_pin2nr(struct sm501_devdata *sm, unsigned int pin)
|
2008-07-25 10:46:01 +02:00
|
|
|
{
|
|
|
|
struct sm501_gpio *gpio = &sm->gpio;
|
2008-07-25 10:46:03 +02:00
|
|
|
int base = (pin < 32) ? gpio->low.gpio.base : gpio->high.gpio.base;
|
|
|
|
|
|
|
|
return (pin % 32) + base;
|
2008-07-25 10:46:01 +02:00
|
|
|
}
|
2008-07-25 10:46:02 +02:00
|
|
|
|
|
|
|
static inline int sm501_gpio_isregistered(struct sm501_devdata *sm)
|
|
|
|
{
|
|
|
|
return sm->gpio.registered;
|
|
|
|
}
|
2008-07-25 10:45:59 +02:00
|
|
|
#else
|
2008-07-25 10:46:02 +02:00
|
|
|
static inline int sm501_register_gpio(struct sm501_devdata *sm)
|
2008-07-25 10:45:59 +02:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-07-25 10:46:02 +02:00
|
|
|
static inline void sm501_gpio_remove(struct sm501_devdata *sm)
|
2008-07-25 10:45:59 +02:00
|
|
|
{
|
|
|
|
}
|
2008-07-25 10:46:01 +02:00
|
|
|
|
2008-07-25 10:46:02 +02:00
|
|
|
static inline int sm501_gpio_pin2nr(struct sm501_devdata *sm, unsigned int pin)
|
2008-07-25 10:46:01 +02:00
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
2008-07-25 10:46:02 +02:00
|
|
|
|
|
|
|
static inline int sm501_gpio_isregistered(struct sm501_devdata *sm)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
2008-07-25 10:45:59 +02:00
|
|
|
#endif
|
|
|
|
|
2008-07-25 10:46:01 +02:00
|
|
|
static int sm501_register_gpio_i2c_instance(struct sm501_devdata *sm,
|
|
|
|
struct sm501_platdata_gpio_i2c *iic)
|
|
|
|
{
|
|
|
|
struct i2c_gpio_platform_data *icd;
|
|
|
|
struct platform_device *pdev;
|
|
|
|
|
|
|
|
pdev = sm501_create_subdev(sm, "i2c-gpio", 0,
|
|
|
|
sizeof(struct i2c_gpio_platform_data));
|
|
|
|
if (!pdev)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
icd = pdev->dev.platform_data;
|
|
|
|
|
|
|
|
/* We keep the pin_sda and pin_scl fields relative in case the
|
|
|
|
* same platform data is passed to >1 SM501.
|
|
|
|
*/
|
|
|
|
|
|
|
|
icd->sda_pin = sm501_gpio_pin2nr(sm, iic->pin_sda);
|
|
|
|
icd->scl_pin = sm501_gpio_pin2nr(sm, iic->pin_scl);
|
|
|
|
icd->timeout = iic->timeout;
|
|
|
|
icd->udelay = iic->udelay;
|
|
|
|
|
|
|
|
/* note, we can't use either of the pin numbers, as the i2c-gpio
|
|
|
|
* driver uses the platform.id field to generate the bus number
|
|
|
|
* to register with the i2c core; The i2c core doesn't have enough
|
|
|
|
* entries to deal with anything we currently use.
|
|
|
|
*/
|
|
|
|
|
|
|
|
pdev->id = iic->bus_num;
|
|
|
|
|
|
|
|
dev_info(sm->dev, "registering i2c-%d: sda=%d (%d), scl=%d (%d)\n",
|
|
|
|
iic->bus_num,
|
|
|
|
icd->sda_pin, iic->pin_sda, icd->scl_pin, iic->pin_scl);
|
|
|
|
|
|
|
|
return sm501_register_device(sm, pdev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sm501_register_gpio_i2c(struct sm501_devdata *sm,
|
|
|
|
struct sm501_platdata *pdata)
|
|
|
|
{
|
|
|
|
struct sm501_platdata_gpio_i2c *iic = pdata->gpio_i2c;
|
|
|
|
int index;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
for (index = 0; index < pdata->gpio_i2c_nr; index++, iic++) {
|
|
|
|
ret = sm501_register_gpio_i2c_instance(sm, iic);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
/* 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
|
2007-06-24 02:16:29 +02:00
|
|
|
*
|
|
|
|
* clear the bits which are set in r->mask, and then set
|
|
|
|
* the bits set in r->set.
|
2007-02-20 22:58:01 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
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->mask;
|
2007-06-24 02:16:29 +02:00
|
|
|
tmp |= r->set;
|
2007-02-20 22:58:01 +01:00
|
|
|
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->m1xclk) {
|
|
|
|
dev_info(sm->dev, "setting M1XCLK to %ld\n", init->m1xclk);
|
|
|
|
sm501_set_clock(sm->dev, SM501_CLOCK_M1XCLK, init->m1xclk);
|
|
|
|
}
|
2007-06-24 02:16:29 +02:00
|
|
|
|
|
|
|
if (init->mclk) {
|
|
|
|
dev_info(sm->dev, "setting MCLK to %ld\n", init->mclk);
|
|
|
|
sm501_set_clock(sm->dev, SM501_CLOCK_MCLK, init->mclk);
|
|
|
|
}
|
2007-06-24 02:16:30 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the PLL sources for the M1CLK and M1XCLK
|
|
|
|
*
|
|
|
|
* If the M1CLK and M1XCLKs are not sourced from the same PLL, then
|
|
|
|
* there is a risk (see errata AB-5) that the SM501 will cease proper
|
|
|
|
* function. If this happens, then it is likely the SM501 will
|
|
|
|
* hang the system.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int sm501_check_clocks(struct sm501_devdata *sm)
|
|
|
|
{
|
|
|
|
unsigned long pwrmode = readl(sm->regs + SM501_CURRENT_CLOCK);
|
|
|
|
unsigned long msrc = (pwrmode & SM501_POWERMODE_M_SRC);
|
|
|
|
unsigned long m1src = (pwrmode & SM501_POWERMODE_M1_SRC);
|
|
|
|
|
|
|
|
return ((msrc == 0 && m1src != 0) || (msrc != 0 && m1src == 0));
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2009-02-17 09:10:19 +01:00
|
|
|
static int __devinit sm501_init_dev(struct sm501_devdata *sm)
|
2007-02-20 22:58:01 +01:00
|
|
|
{
|
2008-04-28 11:14:22 +02:00
|
|
|
struct sm501_initdata *idata;
|
2008-07-25 10:46:01 +02:00
|
|
|
struct sm501_platdata *pdata;
|
2007-02-20 22:58:01 +01:00
|
|
|
resource_size_t mem_avail;
|
|
|
|
unsigned long dramctrl;
|
2007-06-24 02:16:31 +02:00
|
|
|
unsigned long devid;
|
2007-02-20 22:58:01 +01:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
mutex_init(&sm->clock_lock);
|
|
|
|
spin_lock_init(&sm->reg_lock);
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&sm->devices);
|
|
|
|
|
2007-06-24 02:16:31 +02:00
|
|
|
devid = readl(sm->regs + SM501_DEVICEID);
|
2007-02-20 22:58:01 +01:00
|
|
|
|
2007-06-24 02:16:31 +02:00
|
|
|
if ((devid & SM501_DEVICEID_IDMASK) != SM501_DEVICEID_SM501) {
|
|
|
|
dev_err(sm->dev, "incorrect device id %08lx\n", devid);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2008-04-28 11:14:22 +02:00
|
|
|
/* disable irqs */
|
|
|
|
writel(0, sm->regs + SM501_IRQ_MASK);
|
|
|
|
|
2007-06-24 02:16:31 +02:00
|
|
|
dramctrl = readl(sm->regs + SM501_DRAM_CONTROL);
|
2007-02-20 22:58:01 +01:00
|
|
|
mem_avail = sm501_mem_local[(dramctrl >> 13) & 0x7];
|
|
|
|
|
2007-06-24 02:16:31 +02:00
|
|
|
dev_info(sm->dev, "SM501 At %p: Version %08lx, %ld Mb, IRQ %d\n",
|
|
|
|
sm->regs, devid, (unsigned long)mem_avail >> 20, sm->irq);
|
2007-02-20 22:58:01 +01:00
|
|
|
|
2008-03-04 23:28:50 +01:00
|
|
|
sm->rev = devid & SM501_DEVICEID_REVMASK;
|
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
sm501_dump_gate(sm);
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
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 */
|
|
|
|
|
2008-07-25 10:46:01 +02:00
|
|
|
pdata = sm->platdata;
|
|
|
|
idata = pdata ? pdata->init : NULL;
|
|
|
|
|
2008-04-28 11:14:22 +02:00
|
|
|
if (idata) {
|
|
|
|
sm501_init_regs(sm, idata);
|
2007-02-20 22:58:01 +01:00
|
|
|
|
2008-04-28 11:14:22 +02:00
|
|
|
if (idata->devices & SM501_USE_USB_HOST)
|
|
|
|
sm501_register_usbhost(sm, &mem_avail);
|
|
|
|
if (idata->devices & (SM501_USE_UART0 | SM501_USE_UART1))
|
|
|
|
sm501_register_uart(sm, idata->devices);
|
2008-07-25 10:45:59 +02:00
|
|
|
if (idata->devices & SM501_USE_GPIO)
|
|
|
|
sm501_register_gpio(sm);
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
|
|
|
|
2008-07-25 10:46:01 +02:00
|
|
|
if (pdata->gpio_i2c != NULL && pdata->gpio_i2c_nr > 0) {
|
2008-07-25 10:46:02 +02:00
|
|
|
if (!sm501_gpio_isregistered(sm))
|
|
|
|
dev_err(sm->dev, "no gpio available for i2c gpio.\n");
|
2008-07-25 10:46:01 +02:00
|
|
|
else
|
|
|
|
sm501_register_gpio_i2c(sm, pdata);
|
|
|
|
}
|
|
|
|
|
2007-06-24 02:16:30 +02:00
|
|
|
ret = sm501_check_clocks(sm);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(sm->dev, "M1X and M clocks sourced from different "
|
|
|
|
"PLLs\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
/* always create a framebuffer */
|
|
|
|
sm501_register_display(sm, &mem_avail);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-02-17 09:10:19 +01:00
|
|
|
static int __devinit sm501_plat_probe(struct platform_device *dev)
|
2007-02-20 22:58:01 +01:00
|
|
|
{
|
|
|
|
struct sm501_devdata *sm;
|
2008-10-22 01:39:55 +02:00
|
|
|
int ret;
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
sm = kzalloc(sizeof(struct sm501_devdata), GFP_KERNEL);
|
|
|
|
if (sm == NULL) {
|
|
|
|
dev_err(&dev->dev, "no memory for device data\n");
|
2008-10-22 01:39:55 +02:00
|
|
|
ret = -ENOMEM;
|
2007-02-20 22:58:01 +01:00
|
|
|
goto err1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sm->dev = &dev->dev;
|
|
|
|
sm->pdev_id = dev->id;
|
|
|
|
sm->platdata = dev->dev.platform_data;
|
|
|
|
|
2008-10-22 01:39:55 +02:00
|
|
|
ret = platform_get_irq(dev, 0);
|
|
|
|
if (ret < 0) {
|
2007-02-20 22:58:01 +01:00
|
|
|
dev_err(&dev->dev, "failed to get irq resource\n");
|
|
|
|
goto err_res;
|
|
|
|
}
|
2008-10-22 01:39:55 +02:00
|
|
|
sm->irq = ret;
|
2007-02-20 22:58:01 +01:00
|
|
|
|
2008-10-22 01:39:55 +02:00
|
|
|
sm->io_res = platform_get_resource(dev, IORESOURCE_MEM, 1);
|
|
|
|
sm->mem_res = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
2007-02-20 22:58:01 +01:00
|
|
|
if (sm->io_res == NULL || sm->mem_res == NULL) {
|
|
|
|
dev_err(&dev->dev, "failed to get IO resource\n");
|
2008-10-22 01:39:55 +02:00
|
|
|
ret = -ENOENT;
|
2007-02-20 22:58:01 +01:00
|
|
|
goto err_res;
|
|
|
|
}
|
|
|
|
|
|
|
|
sm->regs_claim = request_mem_region(sm->io_res->start,
|
2010-03-08 20:07:48 +01:00
|
|
|
0x100, "sm501");
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
if (sm->regs_claim == NULL) {
|
|
|
|
dev_err(&dev->dev, "cannot claim registers\n");
|
2008-10-22 01:39:55 +02:00
|
|
|
ret = -EBUSY;
|
2007-02-20 22:58:01 +01:00
|
|
|
goto err_res;
|
|
|
|
}
|
|
|
|
|
|
|
|
platform_set_drvdata(dev, sm);
|
|
|
|
|
2010-01-05 19:55:54 +01:00
|
|
|
sm->regs = ioremap(sm->io_res->start, resource_size(sm->io_res));
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
if (sm->regs == NULL) {
|
|
|
|
dev_err(&dev->dev, "cannot remap registers\n");
|
2008-10-22 01:39:55 +02:00
|
|
|
ret = -EIO;
|
2007-02-20 22:58:01 +01:00
|
|
|
goto err_claim;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sm501_init_dev(sm);
|
|
|
|
|
|
|
|
err_claim:
|
|
|
|
release_resource(sm->regs_claim);
|
|
|
|
kfree(sm->regs_claim);
|
|
|
|
err_res:
|
|
|
|
kfree(sm);
|
|
|
|
err1:
|
2008-10-22 01:39:55 +02:00
|
|
|
return ret;
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
#ifdef CONFIG_PM
|
2008-07-25 10:45:58 +02:00
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
/* power management support */
|
|
|
|
|
2008-07-25 10:45:58 +02:00
|
|
|
static void sm501_set_power(struct sm501_devdata *sm, int on)
|
|
|
|
{
|
|
|
|
struct sm501_platdata *pd = sm->platdata;
|
|
|
|
|
|
|
|
if (pd == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (pd->get_power) {
|
|
|
|
if (pd->get_power(sm->dev) == on) {
|
|
|
|
dev_dbg(sm->dev, "is already %d\n", on);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pd->set_power) {
|
|
|
|
dev_dbg(sm->dev, "setting power to %d\n", on);
|
|
|
|
|
|
|
|
pd->set_power(sm->dev, on);
|
|
|
|
sm501_mdelay(sm, 10);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
static int sm501_plat_suspend(struct platform_device *pdev, pm_message_t state)
|
|
|
|
{
|
|
|
|
struct sm501_devdata *sm = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
sm->in_suspend = 1;
|
|
|
|
sm->pm_misc = readl(sm->regs + SM501_MISC_CONTROL);
|
|
|
|
|
|
|
|
sm501_dump_regs(sm);
|
2008-07-25 10:45:58 +02:00
|
|
|
|
|
|
|
if (sm->platdata) {
|
|
|
|
if (sm->platdata->flags & SM501_FLAG_SUSPEND_OFF)
|
|
|
|
sm501_set_power(sm, 0);
|
|
|
|
}
|
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sm501_plat_resume(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct sm501_devdata *sm = platform_get_drvdata(pdev);
|
|
|
|
|
2008-07-25 10:45:58 +02:00
|
|
|
sm501_set_power(sm, 1);
|
|
|
|
|
2007-06-24 02:16:28 +02:00
|
|
|
sm501_dump_regs(sm);
|
|
|
|
sm501_dump_gate(sm);
|
|
|
|
sm501_dump_clk(sm);
|
|
|
|
|
|
|
|
/* check to see if we are in the same state as when suspended */
|
|
|
|
|
|
|
|
if (readl(sm->regs + SM501_MISC_CONTROL) != sm->pm_misc) {
|
|
|
|
dev_info(sm->dev, "SM501_MISC_CONTROL changed over sleep\n");
|
|
|
|
writel(sm->pm_misc, sm->regs + SM501_MISC_CONTROL);
|
|
|
|
|
|
|
|
/* our suspend causes the controller state to change,
|
|
|
|
* either by something attempting setup, power loss,
|
|
|
|
* or an external reset event on power change */
|
|
|
|
|
|
|
|
if (sm->platdata && sm->platdata->init) {
|
|
|
|
sm501_init_regs(sm, sm->platdata->init);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* dump our state from resume */
|
|
|
|
|
|
|
|
sm501_dump_regs(sm);
|
|
|
|
sm501_dump_clk(sm);
|
|
|
|
|
|
|
|
sm->in_suspend = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define sm501_plat_suspend NULL
|
|
|
|
#define sm501_plat_resume NULL
|
|
|
|
#endif
|
|
|
|
|
2007-02-20 22:58:01 +01:00
|
|
|
/* 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,
|
2007-06-24 02:16:30 +02:00
|
|
|
|
|
|
|
/* Errata AB-3 says that 72MHz is the fastest available
|
|
|
|
* for 33MHZ PCI with proper bus-mastering operation */
|
|
|
|
|
|
|
|
.mclk = 72 * MHZ,
|
|
|
|
.m1xclk = 144 * MHZ,
|
2007-02-20 22:58:01 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
2008-07-25 10:46:00 +02:00
|
|
|
.gpio_base = -1,
|
2007-02-20 22:58:01 +01:00
|
|
|
};
|
|
|
|
|
2009-02-17 09:10:19 +01:00
|
|
|
static int __devinit sm501_pci_probe(struct pci_dev *dev,
|
|
|
|
const struct pci_device_id *id)
|
2007-02-20 22:58:01 +01:00
|
|
|
{
|
|
|
|
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,
|
2010-03-08 20:07:48 +01:00
|
|
|
0x100, "sm501");
|
2007-02-20 22:58:01 +01:00
|
|
|
if (sm->regs_claim == NULL) {
|
|
|
|
dev_err(&dev->dev, "cannot claim registers\n");
|
|
|
|
err= -EBUSY;
|
|
|
|
goto err3;
|
|
|
|
}
|
|
|
|
|
2008-09-29 01:14:52 +02:00
|
|
|
sm->regs = pci_ioremap_bar(dev, 1);
|
2007-02-20 22:58:01 +01:00
|
|
|
|
|
|
|
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);
|
2008-07-25 10:45:59 +02:00
|
|
|
|
2008-07-25 10:46:02 +02:00
|
|
|
sm501_gpio_remove(sm);
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
|
|
|
|
2009-02-17 09:10:19 +01:00
|
|
|
static void __devexit sm501_pci_remove(struct pci_dev *dev)
|
2007-02-20 22:58:01 +01:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
2009-02-17 09:10:19 +01:00
|
|
|
static struct pci_driver sm501_pci_driver = {
|
2007-02-20 22:58:01 +01:00
|
|
|
.name = "sm501",
|
|
|
|
.id_table = sm501_pci_tbl,
|
|
|
|
.probe = sm501_pci_probe,
|
2009-02-17 09:10:19 +01:00
|
|
|
.remove = __devexit_p(sm501_pci_remove),
|
2007-02-20 22:58:01 +01:00
|
|
|
};
|
|
|
|
|
2008-07-25 10:45:47 +02:00
|
|
|
MODULE_ALIAS("platform:sm501");
|
|
|
|
|
2009-02-17 09:10:19 +01:00
|
|
|
static struct platform_driver sm501_plat_driver = {
|
2007-02-20 22:58:01 +01:00
|
|
|
.driver = {
|
|
|
|
.name = "sm501",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
},
|
|
|
|
.probe = sm501_plat_probe,
|
|
|
|
.remove = sm501_plat_remove,
|
2007-06-24 02:16:28 +02:00
|
|
|
.suspend = sm501_plat_suspend,
|
|
|
|
.resume = sm501_plat_resume,
|
2007-02-20 22:58:01 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
static int __init sm501_base_init(void)
|
|
|
|
{
|
2009-02-17 09:10:19 +01:00
|
|
|
platform_driver_register(&sm501_plat_driver);
|
|
|
|
return pci_register_driver(&sm501_pci_driver);
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit sm501_base_exit(void)
|
|
|
|
{
|
2009-02-17 09:10:19 +01:00
|
|
|
platform_driver_unregister(&sm501_plat_driver);
|
|
|
|
pci_unregister_driver(&sm501_pci_driver);
|
2007-02-20 22:58:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|