Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756266AbYHYKnk (ORCPT ); Mon, 25 Aug 2008 06:43:40 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1753046AbYHYKnb (ORCPT ); Mon, 25 Aug 2008 06:43:31 -0400 Received: from smtpauth.hypersurf.com ([209.237.0.8]:59858 "EHLO smtpauth.hypersurf.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753088AbYHYKn2 (ORCPT ); Mon, 25 Aug 2008 06:43:28 -0400 Message-ID: <48B28C62.2000600@hypersurf.com> Date: Mon, 25 Aug 2008 03:41:39 -0700 From: Kevin Diggs User-Agent: Mozilla/5.0 (X11; U; Linux i486; en-US; rv:1.8b2) Gecko/20050724 MIME-Version: 1.0 To: linuxppc-dev@ozlabs.org, linux-kernel@vger.kernel.org Subject: [PATCH 1/4] Add low level PLL config register interface module Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 49764 Lines: 1926 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 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=; + 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; + +@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;} + +@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 +#include "linux/kernel.h" +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include + +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<>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<>cnttz)*15625)+(1UL<< + (cntlz+cnttz-1-6)))>>(cntlz+cnttz-6); */ +#ifdef MULFIRST + usec = ((tmp<>cnttz))<<6; + usec = (usec+(1UL<<(cntlz+cnttz-1)))>>(cntlz+cnttz); +#else + usec = ((tmp<>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%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 + +/* + 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<