[PATCH 1/4] Add low level PLL config register interface module

Kevin Diggs kevdig at hypersurf.com
Mon Aug 25 20:41:39 EST 2008


This adds a small module to handle the low level details of dealing with the
PLL config register (HID1) found in the IBM 750GX. It provides 2 possible
interfaces, both selectable via kernel config options. One is a sysfs attribute
and the other is a normal function API. It is called pll_if.

The determination of the bus frequency is what worked on a PowerMac 8600. Any
suggestions on a more general solution are welcome.

WARNING - I used some #ifdefs - Let the fur fly!

My name is Kevin Diggs and I approve this patch.

Signed-off-by: Kevin Diggs <kevdig at hypersurf.com>
Index: Documentation/cpu-freq/pll.pl
===================================================================
--- /dev/null	2004-08-10 18:55:00.000000000 -0700
+++ Documentation/cpu-freq/pll.pl	2008-08-15 13:42:09.000000000 -0700
@@ -0,0 +1,762 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+pll.pl - Provide user friendly interface to sysfs attribute for the 750GX PLL.
+This uses the pll_if module.
+
+=head1 	SYSNOPSIS
+
+ pll.pl [ -bdhtv ] [ -f { clk | ratio }]
+ b:	print bus frequency
+ d:	dump current pll configuration
+ h:	print simple usage information
+ f:	set frequency to specified clk (MHz) or ratio (r suffix)
+ t:	toggle selected pll. Use with -f to cause a switch to the newly
+ 	modified PLL on lock.
+ v:	enable verbose
+
+=head1 DESCRIPTION
+
+This utility provides a user friendly interface to the sysfs attribute that is
+provided by the pll_if module to control the PLL configuration register (HID1)
+in the IBM PowerPC 750FX and 750GX processors.
+
+=over 4
+
+=item -b
+
+print the bus frequency which is used along with the ratios to compute the
+processor clock frequency.
+
+=pod
+
+The method used to get the bus frequency is to use the value of the
+clock-frequency property from the root of the OF tree.
+
+=item -d
+
+prints the current value of the PLL configuration register in a human readable
+form (well ... more or less).
+
+=item -h
+
+print a simple help message.
+
+=item -t
+
+Toggles the selected PLL that is driving the cpu clock. When used with -f causes
+a switch to the new PLL upon lock (when the lock timeout expires).
+
+=item -v
+
+Enable verbose mode. Mostly just prints the file paths that stuff is pulled
+from.
+
+=item -f
+
+Sets the INACTIVE PLL to the selected clock frequency in MHz. If the value has
+an "r" suffix, it is taken as a ratio. This also recognizes the special value
+"off" (-foff) to turn off the INACTIVE PLL.
+
+=back
+
+=head1 AUTHOR
+
+kevin diggs
+
+=cut
+
+use warnings;
+use Getopt::Std;
+
+#
+#	The layout of the PLL register (HID1) is:
+#
+#	0  4|5 6|7|8| 9 11|12 13|14| 15 |16 20|21 22|23|24 28|29 30| 31
+#	PCE |PRE|v|v| Res | Res |v | PS | PC0 | PR0 |v | PC1 | PR1 |Res
+#                | |             |                   |
+#	 PSTAT1 -| |             |                   |
+#        ECLK -----|             |                   |
+#        PI0 --------------------|                   |
+#	 Res ----------------------------------------|
+#
+#	PCE	PLL0 read-only external config
+#	PRE	PLL0 read-only external range
+#	PSTAT1	PLL status (0 -> PLL0, 1 -> PLL1)
+#	ECLK	1 -> enable clkout pin
+#	PI0	PLL0 control:  0 -> external
+#	PS	PLL select:  0 -> PLL0, 1 -> PLL1
+#	PC0	PLL0 configuration
+#	PR0	PLL0 range
+#	PC1	PLL1 configuration
+#	PR1	PLL1 range
+#
+#	PLL_CFG		bus ratio	PLL_CFG		bus ratio
+#	 00000		   off		 10000		    8
+#	 00001		   off		 10001		   8.5
+#	 00010		 bypass		 10010		    9
+#	 00011		 bypass		 10011		   9.5
+#	 00100		    2		 10100		   10
+#	 00101		   2.5		 10101		   11
+#	 00110		    3		 10110		   12
+#	 00111		   3.5		 10111		   13
+#	 01000		    4		 11000		   14
+#	 01001		   4.5		 11001		   15
+#	 01010		    5		 11010		   16
+#	 01011		   5.5		 11011		   17
+#	 01100		    6		 11100		   18
+#	 01101		   6.5		 11101		   19
+#	 01110		    7		 11110		   20
+#	 01111		   7.5		 11111		   off
+#
+#	PLL_RNG		  range
+#	  00		600 -  900
+#	  01		900 - 1000
+#	  10		500 -  600
+#
+# IBM bit numbering:
+#  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
+# 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6
+#
+# 26 27 28 29 30 31
+#  5  4  3  2  1  0
+#
+*pllcBusFreqFile=\"/proc/device-tree/clock-frequency";
+#*pllcCPUPVR=\"/proc/device-tree/PowerPC,*/cpu-version";
+*pllcSysFS=\"/sys/devices/system/cpu/cpu0/*pll";
+
+#
+# Update the value of the PLL configuration register based on the crap passed
+# in. The upper 8 bits (0 - 7) are read only and will be used as flags to con-
+# trol what we are doing:
+#
+*pllcPLL0_DO_CFG=	\0x80000000;	# PLL0 configuration is valid
+*pllcPLL0_DO_RNG=	\0x40000000;	# PLL0 range is valid
+*pllcPLL1_DO_CFG=	\0x20000000;	# PLL1 configuration is valid
+*pllcPLL1_DO_RNG=	\0x10000000;	# PLL1 range is valid
+*pllcPLL_DO_SEL=	\0x08000000;	# PLL select is valid
+#*pllcPLL0_DO_CONTROL=	\0x04000000;	# PLL0 control is valid
+
+#*pllcPLL0_CONTROL_MASK=	\0x20000;
+*pllcPLL_SEL_MASK=	\0x10000;
+#*pllcPLL0_CFG_MASK=	\0x0f800;
+*pllcPLL0_CFG_SHIFT=	\11;
+#*pllcPLL0_RNG_MASK=	\0x00600;
+*pllcPLL0_RNG_SHIFT=	\9;
+#*pllcPLL1_CFG_MASK=	\0x000f8;
+*pllcPLL1_CFG_SHIFT=	\3;
+#*pllcPLL1_RNG_MASK=	\0x00006;
+*pllcPLL1_RNG_SHIFT=	\1;
+
+sub plliCommaize
+{
+my ($num) = @_;
+
+	1 while $num =~ s/(\d)(\d\d\d)(?!\d)/$1,$2/;
+
+	return $num;
+}
+
+sub plliGetActivePLL
+{
+my ($pll) = @_;
+
+	#
+	# Put PSTAT1 (bit 7 by IBM numbering) into the LSBit position.
+	#
+	$pll = $pll >> 24;
+	$pll = $pll & 0x1;
+
+	return $pll;
+}
+
+sub plliGetPLLRatio
+{
+my ($ratio,$config) = @_;
+
+	#
+	# Turn ratio into a right shift count. 0 -> 11, 1 -> 3
+	#
+	$ratio = ($ratio & 0x1) ^ 1;
+	$ratio = $ratio << 3;
+	$ratio = $ratio + 3;
+
+	$config = $config >> $ratio;
+	$config = $config & 0x1F;
+
+	return $config;
+}
+
+sub plliGetPLLRange
+{
+my ($range,$config) = @_;
+
+	#
+	# Turn range into a right shift count. 0 -> 9, 1 -> 1
+	#
+	$range = ($range & 0x1) ^ 1;
+	$range = $range << 3;
+	$range = $range + 1;
+
+	$config = $config >> $range;
+	$config = $config & 0x3;
+
+	return $config;
+}
+
+sub plliPLLOff
+{
+my ($pll_ratio) = @_;
+
+	return $pll_ratio==0 || $pll_ratio==1 || $pll_ratio==31 ||
+		$pll_ratio==2 || $pll_ratio==3;
+}
+
+sub plliGetBusFreq
+{
+my ($sfile) = @_;
+
+my $open_status;
+my $byte_count;
+my $bus_freq;
+my $bus_freq_unpacked;
+
+	#
+	# Get bus clock frequency. Get this from the root of the device tree in
+	# /proc in the "clock-frequency" property of the root node.
+	#
+	$byte_count=0;
+
+	$open_status=open FH,"<",$sfile;
+	if(!defined $open_status) {die "Can't open $sfile.\n";}
+
+	binmode FH,":raw";
+	$byte_count=read FH, $bus_freq, 4;
+	close FH;
+
+	#
+	# Convert binary in bus_freq to normal perl value
+	#
+	$bus_freq_unpacked=unpack "N",$bus_freq;
+
+	return $bus_freq_unpacked;
+}
+
+sub plliGetPVR
+{
+my ($sfile) = @_;
+
+my $cpu_pvr;
+my $cpu_pvr_unpacked;
+my $processor_version;
+my @pvr_list;
+my $byte_count;
+my $open_status;
+my @out;
+
+	@out=();
+
+	#
+	# Get processor pvr. It can be found in cpu-version property of the
+	# "PowerPC,*" directory.
+	#
+	$byte_count=0;
+	@pvr_list=();
+	@pvr_list=glob $sfile;
+
+	$open_status=open FH,"<",$pvr_list[0];
+	if(!defined $open_status) {die "Can't open $pvr_list[0].\n";}
+
+	binmode FH,":raw";
+	$byte_count=read FH, $cpu_pvr, 4;
+	close FH;
+
+	#
+	# Convert binary in cpu_pvr to normal perl value
+	#
+	$cpu_pvr_unpacked=unpack "N",$cpu_pvr;
+	$processor_version=unpack "n",$cpu_pvr;
+
+	#
+	# Put pvr in index 0, version in 1, globbed file name in 2.
+	#
+	push @out,unpack "N",$cpu_pvr;
+	push @out,unpack "n",$cpu_pvr;
+	push @out,$pvr_list[0];
+
+	return wantarray ? @out:$out[0];
+}
+
+sub plliGetPLL
+{
+my ($sfile) = @_;
+
+my $byte_count;
+my $open_status;
+my @pll_list;
+my $pll;
+my @out;
+
+	@out=();
+
+	#
+	# Get value of pll. It is in /sys/devices/system/cpu/cpu0/ppc750gxpll
+	#
+	$byte_count=0;
+	@pll_list=();
+	@pll_list=glob $sfile;
+
+	$open_status=open FH,"<",$pll_list[0];
+	if(!defined $open_status) {die "Can't open $pll_list[0].\n";}
+
+	#
+	# Currently, this is ascii.
+	#
+	$pll=<FH>;
+	close FH;
+
+	chop $pll;
+
+	#
+	# Stick pll (in ascii?) in element 0. Put globbed file name in 1.
+	#
+	push @out,$pll;
+	push @out,$pll_list[0];
+
+	return wantarray ? @out:$out[0];
+}
+
+sub plliSetPLL
+{
+my ($sfile,$pll) = @_;
+
+my $byte_count;
+my $open_status;
+my @pll_list;
+
+	#
+	# Set value of pll. It is in /sys/devices/system/cpu/cpu0/ppc750gxpll
+	#
+	$byte_count=0;
+	@pll_list=();
+	@pll_list=glob $sfile;
+
+	$open_status=open FH,">",$pll_list[0];
+	if(!defined $open_status) {die "Can't open $pll_list[0].\n";}
+
+	printf FH "%x",$pll;
+	close FH;
+}
+
+sub plliAsciiizePLL
+{
+my ($pll,$bus_clk,$sfile,$verbose) = @_;
+my @range_string = ("default","high","low","reserved");
+my @fmt_list = ("off","bypass","%d","%d.5");
+#
+# This mess represents a 32 element array of 2 bit values to turn the ratio
+# into one of the above format strings.
+#
+my @ratio_fmt = (0xEEEEEE50,0x2AAAAAEE);
+my $temp0;
+my $i;
+my $j;
+my $rat0_ext;
+my $rng0_ext;
+my $pll0_cfg_ext;
+my $rat0;
+my $rng0;
+my $pll0_cfg;
+my $rat1;
+my $rng1;
+my $pll1_cfg;
+my @out;
+
+	@out=();
+
+	$pll=hex $pll;
+
+	#
+	# PCE, bits [0-4]
+	#
+	$temp0=$pll>>27;
+	$temp0=$temp0&0x1F;
+
+	if($temp0>15)
+	{
+		$i=$temp0-16;
+		$j=$ratio_fmt[1];
+	}
+	else
+	{
+		$i=$temp0;
+		$j=$ratio_fmt[0];
+	}
+
+	$rng0=($j>>($i<<1))&0x3;
+
+	if($temp0>20) {$temp0=($temp0-10)<<1;}
+	$rat0_ext=$temp0;
+
+	$pll0_cfg_ext=sprintf $fmt_list[$rng0],$rat0_ext>>1;
+	#
+	# PRE, bits [5-6]
+	#
+	$rng0_ext=($pll>>25)&0x3;
+	#
+	# PC0, bits [16-20]
+	#
+	$temp0=plliGetPLLRatio(0,$pll);
+
+	if($temp0>15)
+	{
+		$i=$temp0-16;
+		$j=$ratio_fmt[1];
+	}
+	else
+	{
+		$i=$temp0;
+		$j=$ratio_fmt[0];
+	}
+
+	$rng0=($j>>($i<<1))&0x3;
+
+	if($temp0>20) {$temp0=($temp0-10)<<1;}
+	$rat0=$temp0;
+
+	$pll0_cfg=sprintf $fmt_list[$rng0],$rat0>>1;
+	#
+	# PR0, bits [21-22]
+	#
+	$rng0=plliGetPLLRange(0,$pll);
+	#
+	# PC1, bits [24-28]
+	#
+	$temp0=plliGetPLLRatio(1,$pll);
+
+	if($temp0>15)
+	{
+		$i=$temp0-16;
+		$j=$ratio_fmt[1];
+	}
+	else
+	{
+		$i=$temp0;
+		$j=$ratio_fmt[0];
+	}
+
+	$rng1=($j>>($i<<1))&0x3;
+
+	if($temp0>20) {$temp0=($temp0-10)<<1;}
+	$rat1=$temp0;
+
+	$pll1_cfg=sprintf $fmt_list[$rng1],$rat1>>1;
+	#
+	# PR1, bits [29-30]
+	#
+	$rng1=plliGetPLLRange(1,$pll);
+
+	push @out,sprintf "0x%08x:  ",$pll;
+	push @out,sprintf "PLL0 external (%s (%s MHz), %s), ",$pll0_cfg_ext,
+		plliCommaize($rat0_ext*$bus_clk/2000000),
+		$range_string[$rng0_ext];
+	push @out,sprintf "PLL %d selected, ",plliGetActivePLL($pll);
+	push @out,sprintf "PLL0 %s, ",($pll>>17)&0x1?"internal":"external";
+	push @out,sprintf "PLL0 (%s (%s MHz), %s), ",$pll0_cfg,
+		plliCommaize($rat0*$bus_clk/2000000),$range_string[$rng0];
+	push @out,sprintf "PLL1 (%s (%s MHz), %s)",$pll1_cfg,
+		plliCommaize($rat1*$bus_clk/2000000),$range_string[$rng1];
+
+	if($verbose)
+	{
+		push @out,sprintf "\n\tFrom file %s.",$sfile;
+	}
+
+#	return wantarray ? @out:$out[0];
+	return @out;
+}
+
+sub plliPrintHelp
+{
+	printf "%s:  [ -bdhtv ] [ -f { clk | ratio }] [ -p pll ] [ -s pll ]\n",
+		$0;
+	printf "\tb:\tprint bus frequency\n";
+	printf "\td:\tdump current pll configuration\n";
+	printf "\tf:\tset frequency to specified clk (MHz) or ratio (r suffix)";
+	printf "\n";
+	printf "\tp:\tuse specified pll (0 or 1)\n";
+	printf "\ts:\tswitch to selected pll (0 or 1)\n";
+	printf "\tt:\ttoggle selected pll. Use with -f to cause a switch to ".
+		"the newly\n\t\tmodified PLL on lock.\n";
+	printf "\tv:\tenable verbose\n";
+}
+
+sub plliPrintBusFreq
+{
+my ($bus_freq,$sfile,$verbose) = @_;
+
+	if($bus_freq>1000000) {$bus_freq=$bus_freq/1000000;}
+
+	$bus_freq=plliCommaize $bus_freq;
+
+	print "System Bus Frequency is $bus_freq MHz.\n";
+	print "\tFrom $sfile.\n" if $verbose;
+}
+
+sub plliTogglePLL
+{
+my ($sfile,$bus_freq,$verbose) = @_;
+
+my @pll;
+my $pll;
+my $active_pll;
+my $active_ratio;
+my $other_pll;
+my $other_ratio;
+my $set_pll;
+
+	@pll=plliGetPLL($sfile);
+	$pll=hex $pll[0];
+
+	$active_pll=plliGetActivePLL($pll);
+	$active_ratio=plliGetPLLRatio $active_pll,$pll;
+
+	#
+	# Make sure the "other" pll is active
+	#
+	$other_pll=$active_pll^1;
+	$other_ratio=plliGetPLLRatio $other_pll,$pll;
+printf "active %d, other %d\n",$active_ratio,$other_ratio;
+
+	if(plliPLLOff($other_ratio))
+	{
+		printf "\"Other\" PLL %d (%d) is off. Can't switch to it.\n",
+			$other_pll,$other_ratio;
+	}
+	else
+	{
+		$set_pll=$pllcPLL_DO_SEL;
+		if($other_pll==1) {$set_pll=$set_pll|$pllcPLL_SEL_MASK;}
+
+
+		if($verbose)
+		{
+			if($active_ratio>20) {$active_ratio=($active_ratio-10)
+				<<1;}
+			if($other_ratio>20) {$other_ratio=($other_ratio-10)
+				<<1;}
+			printf "Switching from PLL %d (%d:  %s MHz) to PLL %d",
+				$active_pll,$active_ratio,plliCommaize(
+				$active_ratio*$bus_freq/2000000),$other_pll;
+			printf " (%d:  %s MHz)\n",$other_ratio,plliCommaize(
+				$other_ratio*$bus_freq/2000000);
+		}
+
+		plliSetPLL $sfile,$set_pll;
+	}
+}
+
+sub plliSetFreq
+{
+my ($sfile,$freq_arg,$pll_num,$pll_set,$bus_freq,$verbose) = @_;
+
+my @pll;
+my $pll;
+my $active_pll;
+my $active_ratio;
+my $set_pll;
+my $new_rat;
+my $new_cfg;
+my $new_rng;
+my $temp;
+my $computed_freq;
+my $cfg_shift;
+my $rng_shift;
+my $freq_copy;
+
+	@pll=plliGetPLL($sfile);
+	$pll=hex $pll[0];
+	$freq_copy=$freq_arg;
+
+	$active_pll=plliGetActivePLL($pll);
+	$active_ratio=plliGetPLLRatio $active_pll,$pll;
+
+	#
+	# Figure out which PLL to use. If the number is passed in via $pll_num,
+	# make sure it is not the active PLL.
+	#
+	if(defined $pll_num)
+	{
+		if($pll_num!=0 && $pll_num!=1)
+		{
+			die sprintf "Specified PLL must be 0 or 1 (%d)\n",
+				$pll_num;
+		}
+
+		if($pll_num==$active_pll)
+		{
+			die sprintf "Can't change active PLL (%d)\n",$pll_num;
+		}
+	}
+	else {$pll_num=$active_pll^1;}
+
+	#
+	# Now analyze the frequency argument. There are three possible formats:
+	#	  i) a straight number is taken as a MHz value
+	#	 ii) a number followed by an 'r' (or 'R') to be used as a ratio
+	#	iii) the string 'off' to turn the PLL off
+	#
+	if($freq_copy eq "off") {$new_rat=0;}
+	elsif($freq_copy=~/([\d]+)[rR]/)
+	{
+		$new_rat=$1;
+
+		#
+		# Check the range of values. Legal values are [2-20]. Have no
+		# idea which are legal for the core at the given bus frequency.
+		#
+		if($new_rat<2 || $new_rat>20)
+		{
+			die sprintf "Specified ratio (%d) is out of range\n",
+				$new_rat;
+		}
+	}
+	elsif($freq_copy=~/([\d]+)/)
+	{
+		$new_rat=$1*1000000;
+
+		#
+		# Convert frequency to ratio by dividing by $bus_freq. Then
+		# make sure the given ratio is within the legal range. (Don't
+		# know if it is valid for the core.)
+		#
+		$temp=$new_rat/$bus_freq;
+		if($temp<2 || $temp>20)
+		{
+			die sprintf "Specified frequency (%d) results in ratio".
+				" (%d) that is out of range\n",$new_rat,$temp;
+		}
+
+		$new_rat=$temp;
+	}
+	else
+	{
+		die sprintf "Can't parse frequency argument (%s)\n",$freq_arg;
+	}
+
+	#
+	# First convert actual bus ratio to CFG specific value. From 2 to 10
+	# just double it. From 11 to 20 add 10.
+#	if($temp0>20) {$temp0=($temp0-10)<<1;}
+	#
+	if($new_rat<11) {$new_cfg=$new_rat<<1;}
+	else {$new_cfg=$new_rat+10;}
+
+	#
+	# Now set the range. I don't know what to do with this? Not sure what
+	# the correct values are for 600,000,000 and 900,000,000 (See table 5-1
+	# in 750GX data sheet).
+	#
+	$computed_freq=$bus_freq*$new_rat;
+	if($computed_freq<600000000) {$new_rng=2;}
+	elsif($computed_freq<900000000) {$new_rng=0;}
+	else {$new_rng=1;}
+
+	if($pll_num==0)
+	{
+		$cfg_shift=$pllcPLL0_CFG_SHIFT;
+		$rng_shift=$pllcPLL0_RNG_SHIFT;
+		$set_pll=$pllcPLL0_DO_CFG|$pllcPLL0_DO_RNG;
+	}
+	else
+	{
+		$cfg_shift=$pllcPLL1_CFG_SHIFT;
+		$rng_shift=$pllcPLL1_RNG_SHIFT;
+		$set_pll=$pllcPLL1_DO_CFG|$pllcPLL1_DO_RNG;
+	}
+
+	$set_pll=$set_pll|($new_cfg<<$cfg_shift)|($new_rng<<$rng_shift);
+
+	#
+	# If the pll_set argument is defined, then also encode a PLL change to
+	# the new PLL
+	#
+	if(defined $pll_set && $freq_arg ne "off")
+	{
+		$set_pll=$set_pll|$pllcPLL_DO_SEL;
+
+		if($pll_num==1) {$set_pll=$set_pll|$pllcPLL_SEL_MASK;}
+	}
+
+	if($verbose)
+	{
+		if($freq_arg eq "off")
+		{
+			printf "Turning PLL %d off\n",$pll_num;
+		}
+		else
+		{
+			printf "Setting PLL %d to %s MHz (%d)\n",$pll_num,
+				plliCommaize($computed_freq/1000000),$new_cfg;
+		}
+
+		if(defined $pll_set && $freq_arg ne "off")
+		{
+			printf "\tAlso switching to the newly modified PLL\n";
+		}
+	}
+
+	plliSetPLL $sfile,$set_pll;
+}
+
+our ($opt_o, $opt_i, $opt_f);
+our %pllgOptHash;
+my $pllgBusFreq;
+my $pllgVerbose;
+my @pvr;
+my @pll;
+
+$pllgVerbose=0;
+
+getopts("bdf:hp:s:tv",\%pllgOptHash);
+
+if(defined $pllgOptHash{h} && $pllgOptHash{h}==1) {plliPrintHelp;}
+
+if(defined $pllgOptHash{v} && $pllgOptHash{v}==1) {$pllgVerbose=1;}
+
+#@pvr=plliGetPVR $pllcCPUPVR;
+#printf "Printed output from plliGetPVR() is %x.\n",plliGetPVR($pllcCPUPVR);
+#printf "CPU PVR from \"%s\" is %8x\n",$pvr[2],$pvr[0];
+#printf "CPU Version is %4x\n",$pvr[1];
+
+$pllgBusFreq=plliGetBusFreq $pllcBusFreqFile;
+
+ at pll=plliGetPLL $pllcSysFS;
+
+if(defined $pllgOptHash{d} && $pllgOptHash{d}==1)
+{
+	print plliAsciiizePLL($pll[0],$pllgBusFreq,$pll[1],$pllgVerbose),"\n";
+
+	#
+	# Only do -d once if we aren't changing the PLL
+	#
+	if(!defined $pllgOptHash{t} && !defined $pllgOptHash{f} && !defined
+		$pllgOptHash{s}) {exit;}
+}
+
+if(defined $pllgOptHash{b} && $pllgOptHash{b}==1) {plliPrintBusFreq
+	$pllgBusFreq,$pllcBusFreqFile,$pllgVerbose;}
+
+if(defined $pllgOptHash{t} && $pllgOptHash{t}==1 && !defined $pllgOptHash{f})
+	{plliTogglePLL $pllcSysFS,$pllgBusFreq,$pllgVerbose;}
+
+if(defined $pllgOptHash{f}) {plliSetFreq $pllcSysFS,$pllgOptHash{f},
+	,$pllgOptHash{p},$pllgOptHash{t},$pllgBusFreq,$pllgVerbose;}
+
+ at pll=plliGetPLL $pllcSysFS;
+
+if(defined $pllgOptHash{d} && $pllgOptHash{d}==1) {print plliAsciiizePLL(
+	$pll[0],$pllgBusFreq,$pll[1],$pllgVerbose),"\n";}
+
+exit;
Index: arch/powerpc/kernel/cpu/pll_if.c
===================================================================
--- /dev/null	2004-08-10 18:55:00.000000000 -0700
+++ arch/powerpc/kernel/cpu/pll_if.c	2008-08-25 01:38:30.000000000 -0700
@@ -0,0 +1,800 @@
+/*
+ * pll_if.c - low level interface to HID1 (PLL config) in the PowerPC 750GX
+ *
+ *  Copyright (C) 2008       kevin Diggs
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  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 of the License, 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.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include "linux/init.h"
+#include "linux/module.h"
+#include <linux/autoconf.h>
+#include "linux/kernel.h"
+#include <linux/errno.h>
+#include <linux/cpu.h>
+#include "linux/of.h"
+#include "linux/notifier.h"
+#include "linux/delay.h"
+#include "linux/completion.h"
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
+#include "linux/sysdev.h"
+#endif
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
+#include "linux/hrtimer.h"
+#endif
+
+#include "asm/time.h"
+#include <asm/mmu.h>
+#include <asm/processor.h>
+#include <asm/pgtable.h>
+#include <asm/cputable.h>
+#include <asm/system.h>
+#include <asm/pll_if.h>
+#include <asm/pll.h>
+#include <asm/smp.h>
+
+MODULE_LICENSE("GPL");
+
+static DECLARE_COMPLETION(pllif_v_exit_completion);
+
+static unsigned int boot_ratio;
+static unsigned int pllif_v_bus_clock;
+
+static unsigned int override_bus_clock = 0;
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
+static enum hrtimer_restart pllif_i_timer_f(struct hrtimer *hrt);
+static struct hrtimer pll_timer;
+static unsigned long hrtimers_got_no_freakin_callback_data;
+#ifdef DEBUG
+cycles_t pll_time_stamp;
+#endif
+#else
+static void pllif_i_timer_f(unsigned long newPLL);
+static struct timer_list pll_timer;
+cycles_t pll_time_stamp;
+#endif
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
+
+unsigned long boot_loops;
+static struct sys_device *sysdev_cpu;
+
+static ssize_t show_ppc750gxpll(struct sys_device *dev, char *buf)
+{
+	return sprintf(buf, "%x\n", get_PLL());
+}
+
+static ssize_t __used store_ppc750gxpll(struct sys_device *dev,
+	const char *buf, size_t count)
+{
+unsigned long pll_ul;
+int ret;
+
+	pr_debug(__FILE__">%s()-%d:  buf=%s\n", __func__, __LINE__, buf);
+
+	ret = strict_strtoul(buf, 16, &pll_ul);
+
+	pr_debug(__FILE__">%s()-%d:  %lx (%lu)\n", __func__, __LINE__, pll_ul,
+		pll_ul);
+
+	if (!ret)
+		ret = 0;
+	else {
+		ret = strlen(buf);
+
+	/*	pllif_modify_PLL((unsigned int)pll_ul,!0); */
+		pllif_modify_PLL((unsigned int)pll_ul, 0);
+	}
+
+	return ret;
+}
+
+static SYSDEV_ATTR(ppc750gxpll, 0600, show_ppc750gxpll, store_ppc750gxpll);
+#endif
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_CPU_FREQ
+struct pllif_t_call_data {
+	void *data;
+	int scalar;
+};
+
+static struct pllif_t_call_data pllif_v_switch_call_data;
+static struct pllif_t_call_data pllif_v_lock_call_data;
+static RAW_NOTIFIER_HEAD(pllif_v_pll_switch_chain);
+static RAW_NOTIFIER_HEAD(pllif_v_pll_lock_chain);
+#endif
+
+/*
+ * This initializes the code for the PLL control:
+ * boot_ratio is used to scale the loops_per_jiffy value from its boot value
+ * boot_loops is the boot value of loops_per_jiffy and is used to compute new
+ * values
+ */
+static int __init init_PLL(void)
+{
+unsigned int temp;
+#ifdef CONFIG_PPC_OF
+const u32 *clk;
+struct device_node *tree_root;
+#endif
+
+	if (!cpu_has_feature(CPU_FTR_DUAL_PLL_750FX))
+		return -ENODEV;
+
+	boot_ratio = 0;
+
+	/*
+	 * See if bus clock override was specified
+	 */
+	if (override_bus_clock)
+		pllif_v_bus_clock = override_bus_clock*1000;
+
+#ifdef CONFIG_PPC_OF
+	/*
+	 * If bus clock is not specified, try to get it via OF
+	 */
+	if (!pllif_v_bus_clock) {
+		/*
+		 * Get root node (aka MacRISC bus)
+		 */
+		tree_root = of_find_node_by_name(NULL, "");
+
+
+		if (tree_root) {
+			clk = of_get_property(tree_root, "clock-frequency",
+				NULL);
+
+			if (clk && *clk)
+				pllif_v_bus_clock = (unsigned int) *clk;
+
+			of_node_put(tree_root);
+
+			pr_debug(__FILE__">%s()-%d:  Bus clock from OF is %u\n",
+				__func__, __LINE__, pllif_v_bus_clock);
+		}
+	}
+#endif /* CONFIG_PPC_OF */
+
+	if (!pllif_v_bus_clock) {
+		pr_err(__FILE__">%s()-%d:  Can't determine bus clock.\n",
+			__func__, __LINE__);
+
+		return -EINVAL;
+	}
+
+	/*
+	 * Make sure the clock frequency is correct
+	 */
+	temp = get_PLL();
+	temp = get_PLL_ratio(get_active_PLL(temp), temp);
+
+	ppc_proc_freq = pllif_cfg_to_freq(temp);
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
+	/*
+	 * Units for boot ratio is halves, i.e. 20 is a ratio of 10.
+	 * From 21 on the returned value needs to be converted to halves.
+	 */
+	if (temp > 20)
+		temp = (temp-10)<<1;
+
+	boot_ratio = temp;
+	boot_loops = loops_per_jiffy;
+
+	/*
+	 * Try to get the cpu sysdev
+	 */
+	sysdev_cpu = get_cpu_sysdev(boot_cpuid);
+
+	if (sysdev_cpu != NULL)
+		sysdev_create_file(sysdev_cpu, &attr_ppc750gxpll);
+#endif
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
+	hrtimer_init(&pll_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+#else
+	init_timer(&pll_timer);
+#endif
+
+	pll_timer.function = pllif_i_timer_f;
+
+	return 0;
+}
+
+/*__initcall(init_PLL); */
+
+static void exit_PLL(void)
+{
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
+	if (sysdev_cpu != NULL)
+		sysdev_remove_file(sysdev_cpu, &attr_ppc750gxpll);
+#endif
+
+	/*
+	 * Make sure there are no timers pending by making sure we are not
+	 * doing anything
+	 */
+	wait_for_completion(&pllif_v_exit_completion);
+}
+
+module_init(init_PLL);
+module_exit(exit_PLL);
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
+static unsigned long pllif_i_new_LPJ(unsigned int oldRatio, unsigned int
+	newRatio, unsigned long LPJ)
+{
+	if (LPJ > 200000000)
+		return LPJ/oldRatio*newRatio;
+	else
+		return LPJ*newRatio/oldRatio;
+}
+
+static inline void pllif_i_update_LPJ(unsigned int oldRatio, unsigned int
+	newRatio, unsigned long LPJ)
+{
+	loops_per_jiffy = pllif_i_new_LPJ(oldRatio, newRatio, LPJ);
+}
+#else
+#define pllif_i_update_LPJ(a, b, c)
+#endif
+
+static void pllif_i_switch_PLLs(unsigned int newPLL)
+{
+#if 0
+unsigned long flags;
+#endif
+unsigned int new_ratio, new_ratio_cp, old_ratio, current_pll, masked_boot_ratio;
+
+	pr_debug(__FILE__">%s()-%d:  newPLL=0x%08x\n", __func__, __LINE__,
+		newPLL);
+
+	/*
+	 * Compute new loops_per_jiffy
+	 */
+	current_pll = get_PLL();
+	new_ratio = get_PLL_ratio(get_next_PLL(newPLL), current_pll);
+	old_ratio = get_PLL_ratio(get_active_PLL(current_pll), current_pll);
+	masked_boot_ratio = boot_ratio&0xff;
+	new_ratio_cp = new_ratio;
+
+	pr_debug(__FILE__">%s()-%d:  current_pll=0x%08x, new=%d, old=%d\n",
+		__func__, __LINE__, current_pll, new_ratio, old_ratio);
+
+	current_pll = (current_pll&~PLL_SEL_MASK)|(newPLL&PLL_SEL_MASK);
+
+	pr_debug(__FILE__">%s()-%d:  current_pll=0x%08x, new=%d, old=%d\n",
+		__func__, __LINE__, current_pll, new_ratio, old_ratio);
+
+	/*
+	 * Convert to halves
+	 */
+	if (new_ratio > 20)
+		new_ratio = (new_ratio-10)<<1;
+	if (old_ratio > 20)
+		old_ratio = (old_ratio-10)<<1;
+
+	/*
+	 * Make sure that we never shorten the sleep values
+	 */
+	if (new_ratio > old_ratio) {
+		if (newPLL&PLL_DO_LPJ)
+			pllif_i_update_LPJ(masked_boot_ratio, new_ratio,
+				boot_loops);
+
+		pr_debug(__FILE__">%s()-%d:  masked_boot_ratio=%d, new_ratio="
+		"%d, boot_loops=%ld, loops_per_jiffy=%ld\n", __func__, __LINE__,
+		masked_boot_ratio, new_ratio, boot_loops, loops_per_jiffy);
+
+		set_PLL(current_pll);
+	} else {
+		pr_debug(__FILE__">%s()-%d:  masked_boot_ratio=%d, new_"
+			"ratio=%d, boot_loops=%ld, loops_per_jiffy=%ld\n",
+			__func__, __LINE__, masked_boot_ratio, new_ratio,
+			boot_loops, loops_per_jiffy);
+
+		set_PLL(current_pll);
+
+		if (newPLL&PLL_DO_LPJ)
+			pllif_i_update_LPJ(masked_boot_ratio, new_ratio,
+				boot_loops);
+
+		pr_debug(__FILE__">%s()-%d:  masked_boot_ratio=%d, new_"
+			"ratio=%d, boot_loops=%ld, loops_per_jiffy=%ld\n",
+			__func__, __LINE__, masked_boot_ratio, new_ratio,
+			boot_loops, loops_per_jiffy);
+	}
+
+	raw_notifier_call_chain(&pllif_v_pll_switch_chain, pllifmPllSwitch,
+		pllif_v_switch_call_data.data);
+
+	/*
+	 * This is used to print the clock frequency in /proc/cpuinfo
+	 */
+	ppc_proc_freq = pllif_cfg_to_freq(new_ratio_cp);
+	pr_debug(__FILE__">%s()-%d:  pllif_cfg_to_freq(%u)=%lu\n", __func__,
+		__LINE__, new_ratio_cp, ppc_proc_freq);
+
+#if 0
+	save_flags(flags);
+	cli();
+
+	loops_per_jiffy = pllif_i_new_LPJ(masked_boot_ratio, new_ratio,
+		boot_loops);
+
+	set_PLL(current_pll);
+
+	restore_flags(flags);
+#endif
+}
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
+static enum hrtimer_restart pllif_i_timer_f(struct hrtimer *hrt)
+{
+#ifdef DEBUG
+cycles_t now;
+cycles_t usec, tmp, cntlz, cnttz;
+
+	now = get_cycles();
+
+	now = now-pll_time_stamp;
+
+	/*
+	 * Aw cmon, I'm just havin' a little fun with PPC assembly.
+	 * Just wish I could find a way to use an rlwinm ...
+	 */
+	cnttz = tb_ticks_per_sec; /* needed to get the assembly to ...
+				 * look right ... ??? */
+	tmp = now*15625;
+
+	asm (
+		"addi %0,%3,-1\n\t"
+		"andc %1,%3,%0\n\t"
+		"cntlzw %1,%1\n\t"
+		"subfic %1,%1,31\n\t"
+		"cntlzw %0,%2\n\t":
+		"=r"(cntlz), "=r"(cnttz):
+		"r"(tmp), "b"(cnttz)
+	);
+
+	/*
+	 * 1,000,000 usec per sec and 1,000,000 is 15625<<6
+	 */
+	usec = ((tmp<<cntlz)/(tb_ticks_per_sec>>cnttz))<<6;
+	usec = (usec+(1UL<<(cntlz+cnttz-1)))>>(cntlz+cnttz);
+
+	pr_debug(__FILE__">%s()-%d:  Time delta is %lu cycles, "
+		"%lu uS (cntlz=%lu, cnttz=%lu)\n", __func__, __LINE__, now,
+		usec, cntlz, cnttz);
+#endif
+	raw_notifier_call_chain(&pllif_v_pll_lock_chain, pllifmPllLock,
+		pllif_v_lock_call_data.data);
+
+	/*
+	 * Clear all lock bits
+	 */
+	boot_ratio &= ~(PLL_TIMER|PLL0_LOCK|PLL1_LOCK);
+
+	if ((unsigned int) hrtimers_got_no_freakin_callback_data)
+		pllif_i_switch_PLLs((unsigned int)
+			hrtimers_got_no_freakin_callback_data);
+
+	complete(&pllif_v_exit_completion);
+
+	return HRTIMER_NORESTART;
+}
+#else
+static void pllif_i_timer_f(unsigned long newPLL)
+{
+cycles_t now;
+
+	now = get_cycles();
+	now = now-pll_time_stamp;
+
+#ifdef DEBUG
+	{
+	cycles_t usec, tmp, cntlz, cnttz;
+
+		/*
+		 * Aw cmon, I'm just havin' a little fun with PPC assembly.
+		 * Just wish I could find a way to use an rlwinm ...
+		 */
+		cnttz = tb_ticks_per_sec; /* needed to get the assembly to ...
+					 * look right ... ??? */
+#define MULFIRST
+#ifdef MULFIRST
+		tmp = now*15625;
+#else
+		tmp = now;
+#endif
+
+		asm (
+			"addi %0,%3,-1\n\t"
+			"andc %1,%3,%0\n\t"
+			"cntlzw %1,%1\n\t"
+			"subfic %1,%1,31\n\t"
+			"cntlzw %0,%2\n\t":
+			"=r"(cntlz), "=r"(cnttz):
+			"r"(tmp), "b"(cnttz)
+		);
+
+		/*
+		 * 1,000,000 usec per sec and 1,000,000 is 15625<<6
+		 */
+/*		usec = (((now<<cntlz)/(tb_ticks_per_sec>>cnttz)*15625)+(1UL<<
+			(cntlz+cnttz-1-6)))>>(cntlz+cnttz-6); */
+#ifdef MULFIRST
+		usec = ((tmp<<cntlz)/(tb_ticks_per_sec>>cnttz))<<6;
+		usec = (usec+(1UL<<(cntlz+cnttz-1)))>>(cntlz+cnttz);
+#else
+		usec = ((tmp<<cntlz)/(tb_ticks_per_sec>>cnttz)*15625)<<6;
+		usec = (usec+(1UL<<(cntlz+cnttz-1)))>>(cntlz+cnttz);
+#endif
+
+		pr_debug(__FILE__">%s()-%d:  Time delta is %lu cycles, "
+			"%lu uS (cntlz=%lu, cnttz=%lu)\n", __func__, __LINE__,
+			now, usec, cntlz, cnttz);
+	}
+#endif
+	/*
+	 * Make sure it has been at least 100 usec. 100 usec is 100 *
+	 * tb_ticks_per_sec / 1,000,000 cycles, so:
+	 *	if(now<100*tb_ticks_per_sec/1000000
+	 * 1,000,000 is 15625<<6, so:
+	 *	if((now<<6)<100*tb_ticks_per_sec/15625)
+	 * 100 is 25<<2, so:
+	 *	if((now<<4)<25*tb_ticks_per_sec/15625)
+	 * 15625 is 3125*5, so:
+	 *	if((now<<4)*5<25*tb_ticks_per_sec/3125)
+	 * obviously 25/3125 -> 1/125:
+	 *	if((now<<4)*5<tb_ticks_per_sec/125)
+	 */
+	if ((now<<4)*5 < tb_ticks_per_sec/125)
+		udelay(100-now*1000000/tb_ticks_per_sec);
+
+	raw_notifier_call_chain(&pllif_v_pll_lock_chain, pllifmPllLock,
+		pllif_v_lock_call_data.data);
+
+	/*
+	 * Clear all lock bits
+	 */
+	boot_ratio &= ~(PLL_TIMER|PLL0_LOCK|PLL1_LOCK);
+
+	if ((unsigned int)newPLL)
+		pllif_i_switch_PLLs((unsigned int)newPLL);
+
+	complete(&pllif_v_exit_completion);
+}
+#endif
+
+/*
+ * Handle accesses to the pll register. Examples for write:
+ *	  value		CFGx/RNGx/res	   effect
+ *	0x08010000			switch to PLL1
+ *	0x08000000			switch to PLL0
+ *	0xc000fa00	1111 1/01/0	PLL0 off (CFG/RNG 31/1)
+ *	0xc000f200	1111 0/01/0	PLL0 to 20x (CFG/RNG 30/1)
+ *	0x30000004	0000 0/10/0	PLL1 off (CFG/RNG 0/2)
+ *	0x30000054	0101 0/10/0	PLL1 to 5x (CFG/RNG a/2)
+ */
+/**
+ * modifyPLL: - Takes steps to modify PLL as requested
+ * @pll: Specifies the new value and desired operation to be performed
+ * @scaleLPJ: flag to indicate whether to scale the loops_per_jiffy value
+ *
+ * Based on the value passed in the pll argument, this takes the steps necessary
+ * to change the PLL as requested. The upper 7 or 8 bits of the PLL are read
+ * only. These bit positions in the pll argument are used to specify flags that
+ * indicate the validity of the other fields in the pll argument. See the
+ * pll_if.h header for detail and actual values.
+ */
+int pllif_modify_PLL(unsigned int pll, int scaleLPJ)
+{
+unsigned int current_pll, work_mask, pll_x;
+int rval = 0;
+
+	pr_debug(__FILE__">%s()-%d:\n", __func__, __LINE__);
+	pr_debug(__FILE__">%s()-%d:  pll=0x%08x\n", __func__, __LINE__, pll);
+
+	/*
+	 * This is not reentrant
+	 */
+	if (test_and_set_bit(PLL_LOCK_BIT, (unsigned long *)&boot_ratio)) {
+		pr_debug(__FILE__">%s()-%d:  Busy!\n", __func__, __LINE__);
+		return -EAGAIN;
+	}
+
+	/*
+	 * Don't allow any changes if a timer is pending
+	 */
+	if (test_bit(PLL_TIMER_BIT, (unsigned long *)&boot_ratio))
+		goto checkPLLBusy;
+
+	INIT_COMPLETION(pllif_v_exit_completion);
+
+	current_pll = get_PLL();
+	work_mask = pll>>24;
+
+	/*
+	 * Check to see if the currently selected PLL is being modified
+	 */
+	pll_x = get_active_PLL(current_pll);
+
+	if ((pll_x == 0 && work_mask&(PLL0_DO_CFG|PLL0_DO_RNG|PLL0_DO_CONTROL))
+		|| (pll_x == 1 && work_mask&(PLL1_DO_CFG|PLL1_DO_RNG)))
+		goto checkPLLInVal;
+
+	/*
+	 * Can't change to a PLL that is off. Also can't immediately change to
+	 * one that is not locked. Catch that supposedly impossible condition.
+	 */
+	if (work_mask&PLL_DO_SEL) {
+	int next_ratio;
+	unsigned int which_config;
+
+		pll_x = get_next_PLL(pll);
+
+		/*
+		 * Figure out where the next ratio comes from. It will be from
+		 * pll if we are changing the next pll and current_pll if not.
+		 */
+		which_config = pll_x?((work_mask&PLL1_DO_CFG)?pll:current_pll):
+			((work_mask&PLL0_DO_CFG)?pll:current_pll);
+		next_ratio = get_PLL_ratio(pll_x, which_config);
+		if (next_ratio < 4 || next_ratio > 30)
+			goto checkPLLInVal;
+
+		pll_x = ((pll_x == 0 && boot_ratio&PLL0_LOCK) || (pll_x == 1 &&
+			boot_ratio&PLL1_LOCK))?1:0;
+
+	}
+	/*
+	 * To avoid complications, don't allow both plls to be half ratios
+	 */
+	if (work_mask&PLL0_DO_CFG) {
+	int old_ratio1, new_ratio0;
+
+		old_ratio1 = get_PLL_ratio(1, current_pll);
+		new_ratio0 = get_PLL_ratio(0, pll);
+
+		if (old_ratio1 > 4 && old_ratio1 < 20 && new_ratio0 > 4 &&
+			new_ratio0 < 20 && (old_ratio1&0x1) & (new_ratio0&0x1))
+			goto checkPLLInVal;
+	} else if (work_mask&PLL1_DO_CFG) {
+	int old_ratio0, new_ratio1;
+
+		old_ratio0 = get_PLL_ratio(0, current_pll);
+		new_ratio1 = get_PLL_ratio(1, pll);
+
+		if (old_ratio0 > 4 && old_ratio0 < 20 && new_ratio1 > 4 &&
+			new_ratio1 < 20 && (old_ratio0&0x1) & (new_ratio1&0x1))
+			goto checkPLLInVal;
+	}
+
+	/*
+	 * Determine if we will need to schedule a timer for a PLL relock. If
+	 * any PLL config is being changed then a timer will be needed. Also
+	 * need one if changing to a PLL that is not locked, though that should
+	 * not happen.
+	 */
+	if ((work_mask&(PLL0_DO_CFG|PLL0_DO_RNG|PLL1_DO_CFG|PLL1_DO_RNG|
+		PLL0_DO_CONTROL)) || (work_mask&PLL_DO_SEL && pll_x)) {
+	unsigned int pll_mask, temp;
+
+		pll_mask = 0;
+
+		if (work_mask&PLL0_DO_CFG) {
+			pll_mask |= PLL0_CFG_MASK;
+
+			/*
+			 * Flag that PLL0 needs to relock
+			 */
+			boot_ratio |= PLL0_LOCK;
+		}
+
+		if (work_mask&PLL0_DO_RNG)
+			pll_mask |= PLL0_RNG_MASK;
+
+		if (work_mask&PLL1_DO_CFG) {
+			pll_mask |= PLL1_CFG_MASK;
+
+			/*
+			 * Flag that PLL1 needs to relock
+			 */
+			boot_ratio |= PLL1_LOCK;
+		}
+
+		if (work_mask&PLL1_DO_RNG)
+			pll_mask |= PLL1_RNG_MASK;
+
+		temp = (current_pll&~pll_mask)|(pll&pll_mask);
+
+		if (pll_mask)
+			set_PLL(temp);
+
+		/*
+		 * Flag that a timer is pending
+		 */
+		boot_ratio |= PLL_TIMER;
+
+		/*
+		 * Schedule a timer to clear the PLL lock bits (and signal that
+		 * it is ok to select the PLL)
+		 */
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
+		/*
+		 * Oh please, someone tell me I'm just to stupid to know how
+		 * to pass this to the timer function!
+		 */
+		hrtimers_got_no_freakin_callback_data = (work_mask&PLL_DO_SEL)?
+			(PLL_DO_SEL<<24)|(scaleLPJ?PLL_DO_LPJ:0)|(pll&
+			PLL_SEL_MASK):0;
+
+		pll_timer.expires = ktime_set(0, 100000);
+
+		hrtimer_start(&pll_timer, pll_timer.expires, HRTIMER_MODE_REL);
+#ifdef DEBUG
+		pll_time_stamp = get_cycles();
+#endif
+#else
+		/*
+		 * We might want to pass three pieces of data to the timer
+		 *   i)	that we want to switch PLLs (PLL_DO_SEL)
+		 *  ii)	which PLL to switch to (PLL_SEL_MASK)
+		 * iii) flag to control whether loops_per_jiffy is updated
+		 *      (PLL_DO_LPJ)
+		 */
+		pll_timer.data = (work_mask&PLL_DO_SEL)?(PLL_DO_SEL<<24)|(
+			scaleLPJ?PLL_DO_LPJ:0)|(pll&PLL_SEL_MASK):0;
+
+		/*
+		 * Relock takes 100 us. See how many jiffies will take care of
+		 * it.
+		 */
+		pll_timer.expires = (100*HZ/1000000);
+		if (pll_timer.expires == 0)
+			pll_timer.expires = 1;
+
+		pll_timer.expires = jiffies+pll_timer.expires;
+		add_timer(&pll_timer);
+
+		pll_time_stamp = get_cycles();
+#endif
+	} else if (work_mask&PLL_DO_SEL) {
+		pllif_i_switch_PLLs(pll|(scaleLPJ?PLL_DO_LPJ:0));
+		complete(&pllif_v_exit_completion);
+	}
+
+checkPLLOut:
+	clear_bit(PLL_LOCK_BIT, (unsigned long *)&boot_ratio);
+
+	return rval;
+checkPLLBusy:
+	rval = -EBUSY;
+	goto checkPLLOut;
+checkPLLInVal:
+	rval = -EINVAL;
+	complete(&pllif_v_exit_completion);
+	goto checkPLLOut;
+}
+EXPORT_SYMBOL(pllif_modify_PLL);
+
+/**
+ * pllif_cFg_to_freq: - Takes a ratio and returns the frequency
+ * @cfg: The PLL ratio field value
+ *
+ * This takes a PLL ratio field value and uses it along with the bus frequency
+ * to compute the processor frequency.
+ */
+unsigned int pllif_cfg_to_freq(unsigned int cfg)
+{
+	return (cfg < 21?cfg>>1:cfg-10)*pllif_v_bus_clock;
+}
+EXPORT_SYMBOL(pllif_cfg_to_freq);
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_CPU_FREQ
+/**
+ * pllif_get_bus_clock: - Returns the bus frequency
+ *
+ * This returns the determined bus frequency in Hz.
+ */
+unsigned int pllif_get_bus_clock()
+{
+	return pllif_v_bus_clock;
+}
+EXPORT_SYMBOL(pllif_get_bus_clock);
+
+/**
+ * pllif_register_pll_switch_cb: - Registers a pll switch call back
+ * @nb: structure describing the call back to register
+ *
+ * This registers a call back function that will be called when the clock is
+ * switched from one PLL to the other.
+ */
+int pllif_register_pll_switch_cb(struct notifier_block *nb)
+{
+int ret;
+
+	pllif_v_switch_call_data.data = (void *)nb->next;
+	nb->next = NULL;
+
+	pllif_v_switch_call_data.scalar = nb->priority;
+	nb->priority = 0;
+
+	ret = raw_notifier_chain_register(&pllif_v_pll_switch_chain, nb);
+
+	return ret;
+}
+EXPORT_SYMBOL(pllif_register_pll_switch_cb);
+
+/**
+ * pllif_unregister_pll_switch_cb: - Cancels a previously registered call back
+ * @nb: structure describing the call back to cancel
+ *
+ * This cancels a previously registered switch call back
+ */
+void pllif_unregister_pll_switch_cb(struct notifier_block *nb)
+{
+
+	raw_notifier_chain_unregister(&pllif_v_pll_switch_chain, nb);
+}
+EXPORT_SYMBOL(pllif_unregister_pll_switch_cb);
+
+/**
+ * pllif_register_pll_lock_cb: - Registers a pll lock call back
+ * @nb: structure describing the call back to register
+ *
+ * This registers a call back function that will be called when a PLL has
+ * locked to a new frequency.
+ */
+int pllif_register_pll_lock_cb(struct notifier_block *nb)
+{
+int ret;
+
+	pllif_v_lock_call_data.data = (void *)nb->next;
+	nb->next = NULL;
+
+	pllif_v_lock_call_data.scalar = nb->priority;
+	nb->priority = 0;
+
+	ret = raw_notifier_chain_register(&pllif_v_pll_lock_chain, nb);
+
+	return ret;
+}
+EXPORT_SYMBOL(pllif_register_pll_lock_cb);
+
+/**
+ * pllif_unregister_pll_lock_cb: - Cancels a previously registered call back
+ * @nb: structure describing the call back to cancel
+ *
+ * This cancels a previously registered PLL lock call back
+ */
+void pllif_unregister_pll_lock_cb(struct notifier_block *nb)
+{
+
+	raw_notifier_chain_unregister(&pllif_v_pll_lock_chain, nb);
+}
+EXPORT_SYMBOL(pllif_unregister_pll_lock_cb);
+#endif
+
+module_param(override_bus_clock, uint, 0644);
+MODULE_PARM_DESC(override_bus_clock,
+	"Bus clock frequency in KHz used to compute core clock frequency from"
+	" bus ratios.");
Index: include/asm-powerpc/pll.h
===================================================================
--- /dev/null	2004-08-10 18:55:00.000000000 -0700
+++ include/asm-powerpc/pll.h	2008-08-23 02:05:14.000000000 -0700
@@ -0,0 +1,209 @@
+#ifndef __PLL_H
+#define __PLL_H
+/*
+	Dual PLL functions, for 750FX & 750GX
+	Copyright (C) 2005 by Kevin Diggs
+
+	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 of the License, 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.
+*/
+/*
+	Tue, June 14, 2005.
+	- First public release, contributed by Kevin Diggs.
+	***********
+	***********
+
+	Author:	Kevin Diggs ()
+*/
+
+#include <asm/processor.h>
+
+/*
+	The layout of the PLL register (HID1) is:
+
+	0  4|5 6|7|8| 9 11|12 13|14| 15 |16 20|21 22|23|24 28|29 30| 31
+	PCE |PRE|v|v| Res | Res |v | PS | PC0 | PR0 |v | PC1 | PR1 |Res
+	         | |             |                   |
+	 PSTAT1 -| |             |                   |
+	 ECLK -----|             |                   |
+	 PI0 --------------------|                   |
+	 Res ----------------------------------------|
+
+	PCE	PLL0 read-only external config
+	PRE	PLL0 read-only external range
+	PSTAT1	PLL status (0 -> PLL0, 1 -> PLL1)
+	ECLK	1 -> enable clkout pin
+	PI0	PLL0 control:  0 -> external
+	PS	PLL select:  0 -> PLL0, 1 -> PLL1
+	PC0	PLL0 configuration
+	PR0	PLL0 range
+	PC1	PLL1 configuration
+	PR1	PLL1 range
+
+	PLL_CFG		bus ratio	PLL_CFG		bus ratio
+	 00000		   off		 10000		    8
+	 00001		   off		 10001		   8.5
+	 00010		 bypass		 10010		    9
+	 00011		 bypass		 10011		   9.5
+	 00100		    2		 10100		   10
+	 00101		   2.5		 10101		   11
+	 00110		    3		 10110		   12
+	 00111		   3.5		 10111		   13
+	 01000		    4		 11000		   14
+	 01001		   4.5		 11001		   15
+	 01010		    5		 11010		   16
+	 01011		   5.5		 11011		   17
+	 01100		    6		 11100		   18
+	 01101		   6.5		 11101		   19
+	 01110		    7		 11110		   20
+	 01111		   7.5		 11111		   off
+
+	PLL_RNG		  range
+	  00		600 -  900
+	  01		900 - 1000
+	  10		500 -  600
+ */
+
+/**
+ * get_PLL: - return current value of PLL register (HID1)
+ *
+ * This returns the current value of the PLL configuration register (HID1).
+ */
+static inline volatile unsigned int get_PLL(void)
+{
+unsigned int ret;
+
+	__asm__ __volatile__ ("mfspr %0,%1":
+		"=r"(ret):
+		"i"(SPRN_HID1)
+	);
+
+	return ret;
+}
+
+/**
+ * get_active_PLL: - Returns the active PLL (0 or 1)
+ * @config: The PLL register value to return the active PLL from
+ *
+ * This returns the value of the PSTAT1 bit (bit 7, IBM numbering) , right
+ * justified, which indicates which of the PLLs is currently clocking the CPU.
+ */
+static inline unsigned int get_active_PLL(unsigned int config)
+{
+unsigned int ret;
+
+	/*
+	 * PSTAT1 to LSBit and mask
+	 */
+	__asm__ __volatile__ ("rlwinm %0,%0,8,31,31":
+		"=r"(ret):
+		"0"(config)
+	);
+
+	return ret;
+}
+
+/**
+ * get_next_PLL: - Returns the PLL that is to become active
+ * @config: The PLL register value to return the next PLL from
+ *
+ * This returns the value of the PS bit (bit 15, IBM numbering), right
+ * justified, which indicates which of the PLLs is going to be clocking the CPU.
+ */
+static inline unsigned int get_next_PLL(unsigned int config)
+{
+unsigned int ret;
+
+	/*
+	 * PS to LSBit and mask
+	 */
+	__asm__ __volatile__ ("rlwinm %0,%0,16,31,31":
+		"=r"(ret):
+		"0"(config)
+	);
+
+	return ret;
+}
+
+/**
+ * get_PLL_ratio: - Returns the selected PLL ratio
+ * @ratio: The ratio that is to be returned (0 or 1)
+ * @config: The PLL register value to return the next PLL from
+ *
+ * This returns the value of the selected PLL ratio field (PC0 for 0, PC1 for
+ * 1), right justified. It indicates the frequency of the selected PLL.
+ */
+static inline unsigned int get_PLL_ratio(unsigned int ratio, unsigned int
+	config)
+{
+unsigned int ret;
+
+	/*
+	 * Turn r3 (ratio) into a rotate count for the selected ratio.
+	 * 0 -> 21, 1 -> 29
+	 */
+	__asm__ __volatile__ (
+		"slwi %0,%0,3\n"
+		"addi %0,%0,21\n"
+		"rlwnm %0,%1,%0,27,31\n":
+		"=b"(ret):
+		"r"(config), "0"(ratio)
+	);
+
+	return ret;
+}
+
+/**
+ * get_PLL_range: - Returns the selected PLL range
+ * @range: The range that is to be returned (0 or 1)
+ * @config: The PLL register value to return the next PLL from
+ *
+ * This returns the value of the selected PLL range field (PR0 for 0, PR1 for
+ * 1), right justified.
+ */
+static inline unsigned int get_PLL_range(unsigned int range, unsigned int
+	config)
+{
+unsigned int ret;
+
+	/*
+	 * Turn r3 (range) into a rotate count for the selected range.
+	 * 0 -> 23, 1 -> 31
+	 */
+	__asm__ __volatile__ (
+		"slwi %0,%0,3\n"
+		"addi %0,%0,23\n"
+		"rlwnm %0,%1,%0,30,31\n":
+		"=b"(ret):
+		"r"(config), "0"(range)
+	);
+
+	return ret;
+}
+
+/**
+ * get_PLL: - sets a new value in the PLL register
+ * @config: The new value for the PLL register (HID1)
+ *
+ * This stores a new value in the PLL configuration register. It is possible to
+ * freeze the system by storing certain illegal values.
+ */
+static inline volatile void set_PLL(unsigned int config)
+{
+	__asm__ __volatile__ ("mtspr %1,%0":
+		:
+		"r"(config), "i"(SPRN_HID1)
+	);
+}
+#endif
Index: include/asm-powerpc/pll_if.h
===================================================================
--- /dev/null	2004-08-10 18:55:00.000000000 -0700
+++ include/asm-powerpc/pll_if.h	2008-08-23 02:04:19.000000000 -0700
@@ -0,0 +1,117 @@
+#ifndef __PLL_IF_H
+#define __PLL_IF_H
+/*
+	High Level wrapper functions for Dual PLL in 750FX & 750GX
+	Copyright (C) 2008 by Kevin Diggs
+
+	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 of the License, 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.
+*/
+/*
+	Fri, April 18, 2008.
+	- First public release, contributed by Kevin Diggs.
+	***********
+	***********
+
+	Author:	Kevin Diggs ()
+*/
+
+/*
+ * Update the value of the PLL configuration register based on the crap passed
+ * in. The upper 8 bits (0 - 7) are read only and will be used as flags to con-
+ * trol what we are doing:
+ *	0x80	PLL0 configuration is valid
+ *	0x40	PLL0 range is valid
+ *	0x20	PLL1 configuration is valid
+ *	0x10	PLL1 range is valid
+ *	0x08	PLL select is valid
+ *	0x04	PLL0 control is valid
+ *	0x02	Update loops_per_jiffy value
+ *
+ * Make sure that sufficient time (100 us) is given for a PLL that is changed
+ * to relock before selecting it.
+ */
+#define PLL0_DO_CFG	(0x80)
+#define PLL0_DO_RNG	(0x40)
+#define PLL1_DO_CFG	(0x20)
+#define PLL1_DO_RNG	(0x10)
+#define PLL_DO_SEL	(0x08)
+#define PLL0_DO_CONTROL	(0x04)
+#define PLL_DO_LPJ	(0x02)
+
+#define PLL0_CONTROL_MASK	(0x20000)
+#define PLL_SEL_MASK		(0x10000)
+#define PLL0_CFG_MASK		(0x0f800)
+#define PLL0_CFG_SHIFT		(11)
+#define PLL0_RNG_MASK		(0x00600)
+#define PLL0_RNG_SHIFT		(9)
+#define PLL1_CFG_MASK		(0x000f8)
+#define PLL1_CFG_SHIFT		(3)
+#define PLL1_RNG_MASK		(0x00006)
+#define PLL1_RNG_SHIFT		(1)
+
+#define PLL_LOCK	(0x80000000)		/* Code lock bit */
+#define PLL_LOCK_BIT (31)
+#define PLL_TIMER	(0x40000000)		/* Timer is scheduled */
+#define PLL_TIMER_BIT (30)
+#define PLL0_LOCK	(0x20000000)		/* PLL 0 locking */
+#define PLL0_LOCK_BIT (29)
+#define PLL1_LOCK	(0x10000000)		/* PLL 1 locking */
+#define PLL1_LOCK_BIT (28)
+
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_CPU_FREQ
+#define pllifmPllSwitch		(0x80000000)
+#define pllifmPllLock		(0x40000000)
+
+extern int pllif_modify_PLL(unsigned int newPLL, int scaleLPJ);
+extern unsigned int pllif_get_bus_clock(void);
+extern unsigned int pllif_cfg_to_freq(unsigned int ratio);
+extern int pllif_register_pll_switch_cb(struct notifier_block *nb);
+extern void pllif_unregister_pll_switch_cb(struct notifier_block *nb);
+extern int pllif_register_pll_lock_cb(struct notifier_block *nb);
+extern void pllif_unregister_pll_lock_cb(struct notifier_block *nb);
+
+/**
+ * pllif_get_latency: - Return processor frequency switch latency
+ *
+ * This returns the latency that a processor frequency switch takes. It is in
+ * nano seconds. The value will depend on whether HRTIMERS are being used.
+ */
+static inline int pllif_get_latency(void)
+{
+#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
+	return 100000;
+#else
+	return 1000000000/HZ;
+#endif
+}
+
+/**
+ * pllif_pack_state: - Returns the arguments packed together
+ * @cfg: The ratio that is to be used
+ * @rng: The range that is to be used
+ *
+ * This takes a ratio and range and packs them together in the right positions
+ * relative to each other for creating a new PLL value. The value is positioned
+ * correctly for PLL 1. To reposition for PLL 0 do a left shift of
+ * (PLL0_CFG_SHIFT - PLL1_CFG_SHIFT).
+ */
+static inline unsigned int pllif_pack_state(unsigned int cfg, unsigned int
+	rng)
+{
+	return (cfg<<PLL1_CFG_SHIFT)|(rng<<PLL1_RNG_SHIFT);
+}
+#endif
+
+#endif



More information about the Linuxppc-dev mailing list