Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Copyright (c) 2006-2009 Simtec Electronics
0004  *  http://armlinux.simtec.co.uk/
0005  *  Ben Dooks <ben@simtec.co.uk>
0006  *  Vincent Sanders <vince@simtec.co.uk>
0007  *
0008  * S3C2440/S3C2442 CPU Frequency scaling
0009 */
0010 
0011 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0012 
0013 #include <linux/init.h>
0014 #include <linux/module.h>
0015 #include <linux/interrupt.h>
0016 #include <linux/ioport.h>
0017 #include <linux/cpufreq.h>
0018 #include <linux/device.h>
0019 #include <linux/delay.h>
0020 #include <linux/clk.h>
0021 #include <linux/err.h>
0022 #include <linux/io.h>
0023 #include <linux/soc/samsung/s3c-cpufreq-core.h>
0024 #include <linux/soc/samsung/s3c-pm.h>
0025 
0026 #include <asm/mach/arch.h>
0027 #include <asm/mach/map.h>
0028 
0029 #define S3C2440_CLKDIVN_PDIVN        (1<<0)
0030 #define S3C2440_CLKDIVN_HDIVN_MASK   (3<<1)
0031 #define S3C2440_CLKDIVN_HDIVN_1      (0<<1)
0032 #define S3C2440_CLKDIVN_HDIVN_2      (1<<1)
0033 #define S3C2440_CLKDIVN_HDIVN_4_8    (2<<1)
0034 #define S3C2440_CLKDIVN_HDIVN_3_6    (3<<1)
0035 #define S3C2440_CLKDIVN_UCLK         (1<<3)
0036 
0037 #define S3C2440_CAMDIVN_CAMCLK_MASK  (0xf<<0)
0038 #define S3C2440_CAMDIVN_CAMCLK_SEL   (1<<4)
0039 #define S3C2440_CAMDIVN_HCLK3_HALF   (1<<8)
0040 #define S3C2440_CAMDIVN_HCLK4_HALF   (1<<9)
0041 #define S3C2440_CAMDIVN_DVSEN        (1<<12)
0042 
0043 #define S3C2442_CAMDIVN_CAMCLK_DIV3  (1<<5)
0044 
0045 static struct clk *xtal;
0046 static struct clk *fclk;
0047 static struct clk *hclk;
0048 static struct clk *armclk;
0049 
0050 /* HDIV: 1, 2, 3, 4, 6, 8 */
0051 
0052 static inline int within_khz(unsigned long a, unsigned long b)
0053 {
0054     long diff = a - b;
0055 
0056     return (diff >= -1000 && diff <= 1000);
0057 }
0058 
0059 /**
0060  * s3c2440_cpufreq_calcdivs - calculate divider settings
0061  * @cfg: The cpu frequency settings.
0062  *
0063  * Calcualte the divider values for the given frequency settings
0064  * specified in @cfg. The values are stored in @cfg for later use
0065  * by the relevant set routine if the request settings can be reached.
0066  */
0067 static int s3c2440_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg)
0068 {
0069     unsigned int hdiv, pdiv;
0070     unsigned long hclk, fclk, armclk;
0071     unsigned long hclk_max;
0072 
0073     fclk = cfg->freq.fclk;
0074     armclk = cfg->freq.armclk;
0075     hclk_max = cfg->max.hclk;
0076 
0077     s3c_freq_dbg("%s: fclk is %lu, armclk %lu, max hclk %lu\n",
0078              __func__, fclk, armclk, hclk_max);
0079 
0080     if (armclk > fclk) {
0081         pr_warn("%s: armclk > fclk\n", __func__);
0082         armclk = fclk;
0083     }
0084 
0085     /* if we are in DVS, we need HCLK to be <= ARMCLK */
0086     if (armclk < fclk && armclk < hclk_max)
0087         hclk_max = armclk;
0088 
0089     for (hdiv = 1; hdiv < 9; hdiv++) {
0090         if (hdiv == 5 || hdiv == 7)
0091             hdiv++;
0092 
0093         hclk = (fclk / hdiv);
0094         if (hclk <= hclk_max || within_khz(hclk, hclk_max))
0095             break;
0096     }
0097 
0098     s3c_freq_dbg("%s: hclk %lu, div %d\n", __func__, hclk, hdiv);
0099 
0100     if (hdiv > 8)
0101         goto invalid;
0102 
0103     pdiv = (hclk > cfg->max.pclk) ? 2 : 1;
0104 
0105     if ((hclk / pdiv) > cfg->max.pclk)
0106         pdiv++;
0107 
0108     s3c_freq_dbg("%s: pdiv %d\n", __func__, pdiv);
0109 
0110     if (pdiv > 2)
0111         goto invalid;
0112 
0113     pdiv *= hdiv;
0114 
0115     /* calculate a valid armclk */
0116 
0117     if (armclk < hclk)
0118         armclk = hclk;
0119 
0120     /* if we're running armclk lower than fclk, this really means
0121      * that the system should go into dvs mode, which means that
0122      * armclk is connected to hclk. */
0123     if (armclk < fclk) {
0124         cfg->divs.dvs = 1;
0125         armclk = hclk;
0126     } else
0127         cfg->divs.dvs = 0;
0128 
0129     cfg->freq.armclk = armclk;
0130 
0131     /* store the result, and then return */
0132 
0133     cfg->divs.h_divisor = hdiv;
0134     cfg->divs.p_divisor = pdiv;
0135 
0136     return 0;
0137 
0138  invalid:
0139     return -EINVAL;
0140 }
0141 
0142 #define CAMDIVN_HCLK_HALF (S3C2440_CAMDIVN_HCLK3_HALF | \
0143                S3C2440_CAMDIVN_HCLK4_HALF)
0144 
0145 /**
0146  * s3c2440_cpufreq_setdivs - set the cpu frequency divider settings
0147  * @cfg: The cpu frequency settings.
0148  *
0149  * Set the divisors from the settings in @cfg, which where generated
0150  * during the calculation phase by s3c2440_cpufreq_calcdivs().
0151  */
0152 static void s3c2440_cpufreq_setdivs(struct s3c_cpufreq_config *cfg)
0153 {
0154     unsigned long clkdiv, camdiv;
0155 
0156     s3c_freq_dbg("%s: divisors: h=%d, p=%d\n", __func__,
0157              cfg->divs.h_divisor, cfg->divs.p_divisor);
0158 
0159     clkdiv = s3c24xx_read_clkdivn();
0160     camdiv = s3c2440_read_camdivn();
0161 
0162     clkdiv &= ~(S3C2440_CLKDIVN_HDIVN_MASK | S3C2440_CLKDIVN_PDIVN);
0163     camdiv &= ~CAMDIVN_HCLK_HALF;
0164 
0165     switch (cfg->divs.h_divisor) {
0166     case 1:
0167         clkdiv |= S3C2440_CLKDIVN_HDIVN_1;
0168         break;
0169 
0170     case 2:
0171         clkdiv |= S3C2440_CLKDIVN_HDIVN_2;
0172         break;
0173 
0174     case 6:
0175         camdiv |= S3C2440_CAMDIVN_HCLK3_HALF;
0176         fallthrough;
0177     case 3:
0178         clkdiv |= S3C2440_CLKDIVN_HDIVN_3_6;
0179         break;
0180 
0181     case 8:
0182         camdiv |= S3C2440_CAMDIVN_HCLK4_HALF;
0183         fallthrough;
0184     case 4:
0185         clkdiv |= S3C2440_CLKDIVN_HDIVN_4_8;
0186         break;
0187 
0188     default:
0189         BUG();  /* we don't expect to get here. */
0190     }
0191 
0192     if (cfg->divs.p_divisor != cfg->divs.h_divisor)
0193         clkdiv |= S3C2440_CLKDIVN_PDIVN;
0194 
0195     /* todo - set pclk. */
0196 
0197     /* Write the divisors first with hclk intentionally halved so that
0198      * when we write clkdiv we will under-frequency instead of over. We
0199      * then make a short delay and remove the hclk halving if necessary.
0200      */
0201 
0202     s3c2440_write_camdivn(camdiv | CAMDIVN_HCLK_HALF);
0203     s3c24xx_write_clkdivn(clkdiv);
0204 
0205     ndelay(20);
0206     s3c2440_write_camdivn(camdiv);
0207 
0208     clk_set_parent(armclk, cfg->divs.dvs ? hclk : fclk);
0209 }
0210 
0211 static int run_freq_for(unsigned long max_hclk, unsigned long fclk,
0212             int *divs,
0213             struct cpufreq_frequency_table *table,
0214             size_t table_size)
0215 {
0216     unsigned long freq;
0217     int index = 0;
0218     int div;
0219 
0220     for (div = *divs; div > 0; div = *divs++) {
0221         freq = fclk / div;
0222 
0223         if (freq > max_hclk && div != 1)
0224             continue;
0225 
0226         freq /= 1000; /* table is in kHz */
0227         index = s3c_cpufreq_addfreq(table, index, table_size, freq);
0228         if (index < 0)
0229             break;
0230     }
0231 
0232     return index;
0233 }
0234 
0235 static int hclk_divs[] = { 1, 2, 3, 4, 6, 8, -1 };
0236 
0237 static int s3c2440_cpufreq_calctable(struct s3c_cpufreq_config *cfg,
0238                      struct cpufreq_frequency_table *table,
0239                      size_t table_size)
0240 {
0241     int ret;
0242 
0243     WARN_ON(cfg->info == NULL);
0244     WARN_ON(cfg->board == NULL);
0245 
0246     ret = run_freq_for(cfg->info->max.hclk,
0247                cfg->info->max.fclk,
0248                hclk_divs,
0249                table, table_size);
0250 
0251     s3c_freq_dbg("%s: returning %d\n", __func__, ret);
0252 
0253     return ret;
0254 }
0255 
0256 static struct s3c_cpufreq_info s3c2440_cpufreq_info = {
0257     .max        = {
0258         .fclk   = 400000000,
0259         .hclk   = 133333333,
0260         .pclk   =  66666666,
0261     },
0262 
0263     .locktime_m = 300,
0264     .locktime_u = 300,
0265     .locktime_bits  = 16,
0266 
0267     .name       = "s3c244x",
0268     .calc_iotiming  = s3c2410_iotiming_calc,
0269     .set_iotiming   = s3c2410_iotiming_set,
0270     .get_iotiming   = s3c2410_iotiming_get,
0271     .set_fvco   = s3c2410_set_fvco,
0272 
0273     .set_refresh    = s3c2410_cpufreq_setrefresh,
0274     .set_divs   = s3c2440_cpufreq_setdivs,
0275     .calc_divs  = s3c2440_cpufreq_calcdivs,
0276     .calc_freqtable = s3c2440_cpufreq_calctable,
0277 
0278     .debug_io_show  = s3c_cpufreq_debugfs_call(s3c2410_iotiming_debugfs),
0279 };
0280 
0281 static int s3c2440_cpufreq_add(struct device *dev,
0282                    struct subsys_interface *sif)
0283 {
0284     xtal = s3c_cpufreq_clk_get(NULL, "xtal");
0285     hclk = s3c_cpufreq_clk_get(NULL, "hclk");
0286     fclk = s3c_cpufreq_clk_get(NULL, "fclk");
0287     armclk = s3c_cpufreq_clk_get(NULL, "armclk");
0288 
0289     if (IS_ERR(xtal) || IS_ERR(hclk) || IS_ERR(fclk) || IS_ERR(armclk)) {
0290         pr_err("%s: failed to get clocks\n", __func__);
0291         return -ENOENT;
0292     }
0293 
0294     return s3c_cpufreq_register(&s3c2440_cpufreq_info);
0295 }
0296 
0297 static struct subsys_interface s3c2440_cpufreq_interface = {
0298     .name       = "s3c2440_cpufreq",
0299     .subsys     = &s3c2440_subsys,
0300     .add_dev    = s3c2440_cpufreq_add,
0301 };
0302 
0303 static int s3c2440_cpufreq_init(void)
0304 {
0305     return subsys_interface_register(&s3c2440_cpufreq_interface);
0306 }
0307 
0308 /* arch_initcall adds the clocks we need, so use subsys_initcall. */
0309 subsys_initcall(s3c2440_cpufreq_init);
0310 
0311 static struct subsys_interface s3c2442_cpufreq_interface = {
0312     .name       = "s3c2442_cpufreq",
0313     .subsys     = &s3c2442_subsys,
0314     .add_dev    = s3c2440_cpufreq_add,
0315 };
0316 
0317 static int s3c2442_cpufreq_init(void)
0318 {
0319     return subsys_interface_register(&s3c2442_cpufreq_interface);
0320 }
0321 subsys_initcall(s3c2442_cpufreq_init);