Skip to content

Commit

Permalink
[POWERPC] pasemi: Cpufreq driver
Browse files Browse the repository at this point in the history
Cpufreq driver for PA Semi PWRficient processors.

Signed-off-by: Egor Martovetsky <egor@pasemi.com>
Signed-off-by: Olof Johansson <olof@lixom.net>
Signed-off-by: Paul Mackerras <paulus@samba.org>
  • Loading branch information
olofj authored and paulusmack committed Apr 27, 2007
1 parent 116ab40 commit 2e0c337
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 0 deletions.
10 changes: 10 additions & 0 deletions arch/powerpc/platforms/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,16 @@ config CPU_FREQ_PMAC64
help
This adds support for frequency switching on Apple iMac G5,
and some of the more recent desktop G5 machines as well.

config PPC_PASEMI_CPUFREQ
bool "Support for PA Semi PWRficient"
depends on CPU_FREQ && PPC_PASEMI
default y
select CPU_FREQ_TABLE
help
This adds the support for frequency switching on PA Semi
PWRficient processors.

endmenu

config PPC601_SYNC_FIX
Expand Down
1 change: 1 addition & 0 deletions arch/powerpc/platforms/pasemi/Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
obj-y += setup.o pci.o time.o idle.o powersave.o iommu.o
obj-$(CONFIG_PPC_PASEMI_MDIO) += gpio_mdio.o
obj-$(CONFIG_PPC_PASEMI_CPUFREQ) += cpufreq.o
307 changes: 307 additions & 0 deletions arch/powerpc/platforms/pasemi/cpufreq.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
/*
* Copyright (C) 2007 PA Semi, Inc
*
* Authors: Egor Martovetsky <egor@pasemi.com>
* Olof Johansson <olof@lixom.net>
*
* Maintained by: Olof Johansson <olof@lixom.net>
*
* Based on arch/powerpc/platforms/cell/cbe_cpufreq.c:
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/

#include <linux/cpufreq.h>
#include <linux/timer.h>

#include <asm/hw_irq.h>
#include <asm/io.h>
#include <asm/prom.h>

#define SDCASR_REG 0x0100
#define SDCASR_REG_STRIDE 0x1000
#define SDCPWR_CFGA0_REG 0x0100
#define SDCPWR_PWST0_REG 0x0000
#define SDCPWR_GIZTIME_REG 0x0440

/* SDCPWR_GIZTIME_REG fields */
#define SDCPWR_GIZTIME_GR 0x80000000
#define SDCPWR_GIZTIME_LONGLOCK 0x000000ff

/* Offset of ASR registers from SDC base */
#define SDCASR_OFFSET 0x120000

static void __iomem *sdcpwr_mapbase;
static void __iomem *sdcasr_mapbase;

static DEFINE_MUTEX(pas_switch_mutex);

/* Current astate, is used when waking up from power savings on
* one core, in case the other core has switched states during
* the idle time.
*/
static int current_astate;

/* We support 5(A0-A4) power states excluding turbo(A5-A6) modes */
static struct cpufreq_frequency_table pas_freqs[] = {
{0, 0},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{0, CPUFREQ_TABLE_END},
};

static struct freq_attr *pas_cpu_freqs_attr[] = {
&cpufreq_freq_attr_scaling_available_freqs,
NULL,
};

/*
* hardware specific functions
*/

static int get_astate_freq(int astate)
{
u32 ret;
ret = in_le32(sdcpwr_mapbase + SDCPWR_CFGA0_REG + (astate * 0x10));

return ret & 0x3f;
}

static int get_cur_astate(int cpu)
{
u32 ret;

ret = in_le32(sdcpwr_mapbase + SDCPWR_PWST0_REG);
ret = (ret >> (cpu * 4)) & 0x7;

return ret;
}

static int get_gizmo_latency(void)
{
u32 giztime, ret;

giztime = in_le32(sdcpwr_mapbase + SDCPWR_GIZTIME_REG);

/* just provide the upper bound */
if (giztime & SDCPWR_GIZTIME_GR)
ret = (giztime & SDCPWR_GIZTIME_LONGLOCK) * 128000;
else
ret = (giztime & SDCPWR_GIZTIME_LONGLOCK) * 1000;

return ret;
}

static void set_astate(int cpu, unsigned int astate)
{
u64 flags;

/* Return if called before init has run */
if (unlikely(!sdcasr_mapbase))
return;

local_irq_save(flags);

out_le32(sdcasr_mapbase + SDCASR_REG + SDCASR_REG_STRIDE*cpu, astate);

local_irq_restore(flags);
}

void restore_astate(int cpu)
{
set_astate(cpu, current_astate);
}

/*
* cpufreq functions
*/

static int pas_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
u32 *max_freq;
int i, cur_astate;
struct resource res;
struct device_node *cpu, *dn;
int err = -ENODEV;

cpu = of_get_cpu_node(policy->cpu, NULL);

if (!cpu)
goto out;

dn = of_find_compatible_node(NULL, "sdc", "1682m-sdc");
if (!dn)
goto out;
err = of_address_to_resource(dn, 0, &res);
of_node_put(dn);
if (err)
goto out;
sdcasr_mapbase = ioremap(res.start + SDCASR_OFFSET, 0x2000);
if (!sdcasr_mapbase) {
err = -EINVAL;
goto out;
}

dn = of_find_compatible_node(NULL, "gizmo", "1682m-gizmo");
if (!dn) {
err = -ENODEV;
goto out_unmap_sdcasr;
}
err = of_address_to_resource(dn, 0, &res);
of_node_put(dn);
if (err)
goto out_unmap_sdcasr;
sdcpwr_mapbase = ioremap(res.start, 0x1000);
if (!sdcpwr_mapbase) {
err = -EINVAL;
goto out_unmap_sdcasr;
}

pr_debug("init cpufreq on CPU %d\n", policy->cpu);

max_freq = (u32*) get_property(cpu, "clock-frequency", NULL);
if (!max_freq) {
err = -EINVAL;
goto out_unmap_sdcpwr;
}

/* we need the freq in kHz */
*max_freq /= 1000;

pr_debug("max clock-frequency is at %u kHz\n", *max_freq);
pr_debug("initializing frequency table\n");

/* initialize frequency table */
for (i=0; pas_freqs[i].frequency!=CPUFREQ_TABLE_END; i++) {
pas_freqs[i].frequency = get_astate_freq(pas_freqs[i].index) * 100000;
pr_debug("%d: %d\n", i, pas_freqs[i].frequency);
}

policy->governor = CPUFREQ_DEFAULT_GOVERNOR;

policy->cpuinfo.transition_latency = get_gizmo_latency();

cur_astate = get_cur_astate(policy->cpu);
pr_debug("current astate is at %d\n",cur_astate);

policy->cur = pas_freqs[cur_astate].frequency;
policy->cpus = cpu_online_map;

cpufreq_frequency_table_get_attr(pas_freqs, policy->cpu);

/* this ensures that policy->cpuinfo_min and policy->cpuinfo_max
* are set correctly
*/
return cpufreq_frequency_table_cpuinfo(policy, pas_freqs);

out_unmap_sdcpwr:
iounmap(sdcpwr_mapbase);

out_unmap_sdcasr:
iounmap(sdcasr_mapbase);
out:
return err;
}

static int pas_cpufreq_cpu_exit(struct cpufreq_policy *policy)
{
if (sdcasr_mapbase)
iounmap(sdcasr_mapbase);
if (sdcpwr_mapbase)
iounmap(sdcpwr_mapbase);

cpufreq_frequency_table_put_attr(policy->cpu);
return 0;
}

static int pas_cpufreq_verify(struct cpufreq_policy *policy)
{
return cpufreq_frequency_table_verify(policy, pas_freqs);
}

static int pas_cpufreq_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
struct cpufreq_freqs freqs;
int pas_astate_new;
int i;

cpufreq_frequency_table_target(policy,
pas_freqs,
target_freq,
relation,
&pas_astate_new);

freqs.old = policy->cur;
freqs.new = pas_freqs[pas_astate_new].frequency;
freqs.cpu = policy->cpu;

mutex_lock(&pas_switch_mutex);
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);

pr_debug("setting frequency for cpu %d to %d kHz, 1/%d of max frequency\n",
policy->cpu,
pas_freqs[pas_astate_new].frequency,
pas_freqs[pas_astate_new].index);

current_astate = pas_astate_new;

for_each_online_cpu(i)
set_astate(i, pas_astate_new);

cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
mutex_unlock(&pas_switch_mutex);

return 0;
}

static struct cpufreq_driver pas_cpufreq_driver = {
.name = "pas-cpufreq",
.owner = THIS_MODULE,
.flags = CPUFREQ_CONST_LOOPS,
.init = pas_cpufreq_cpu_init,
.exit = pas_cpufreq_cpu_exit,
.verify = pas_cpufreq_verify,
.target = pas_cpufreq_target,
.attr = pas_cpu_freqs_attr,
};

/*
* module init and destoy
*/

static int __init pas_cpufreq_init(void)
{
if (!machine_is_compatible("PA6T-1682M"))
return -ENODEV;

return cpufreq_register_driver(&pas_cpufreq_driver);
}

static void __exit pas_cpufreq_exit(void)
{
cpufreq_unregister_driver(&pas_cpufreq_driver);
}

module_init(pas_cpufreq_init);
module_exit(pas_cpufreq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>, Olof Johansson <olof@lixom.net>");
9 changes: 9 additions & 0 deletions arch/powerpc/platforms/pasemi/idle.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,22 @@ static int pasemi_system_reset_exception(struct pt_regs *regs)
/* do system reset */
return 0;
}

/* Set higher astate since we come out of power savings at 0 */
restore_astate(hard_smp_processor_id());

/* everything handled */
regs->msr |= MSR_RI;
return 1;
}

void __init pasemi_idle_init(void)
{
#ifndef CONFIG_PPC_PASEMI_CPUFREQ
printk(KERN_WARNING "No cpufreq driver, powersavings modes disabled\n");
current_mode = 0;
#endif

ppc_md.system_reset_exception = pasemi_system_reset_exception;
ppc_md.power_save = modes[current_mode].entry;
printk(KERN_INFO "Using PA6T idle loop (%s)\n", modes[current_mode].name);
Expand Down
8 changes: 8 additions & 0 deletions arch/powerpc/platforms/pasemi/pasemi.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ extern void __init pasemi_idle_init(void);
extern void idle_spin(void);
extern void idle_doze(void);

/* Restore astate to last set */
#ifdef CONFIG_PPC_PASEMI_CPUFREQ
extern void restore_astate(int cpu);
#else
static inline void restore_astate(int cpu)
{
}
#endif


#endif /* _PASEMI_PASEMI_H */

0 comments on commit 2e0c337

Please sign in to comment.