1 /* 2 * Marvell Armada AP806 System Controller 3 * 4 * Copyright (C) 2016 Marvell 5 * 6 * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> 7 * 8 * This file is licensed under the terms of the GNU General Public 9 * License version 2. This program is licensed "as is" without any 10 * warranty of any kind, whether express or implied. 11 */ 12 13 #define pr_fmt(fmt) "ap806-system-controller: " fmt 14 15 #include <linux/clk-provider.h> 16 #include <linux/mfd/syscon.h> 17 #include <linux/module.h> 18 #include <linux/of.h> 19 #include <linux/of_address.h> 20 #include <linux/platform_device.h> 21 #include <linux/regmap.h> 22 23 #define AP806_SAR_REG 0x400 24 #define AP806_SAR_CLKFREQ_MODE_MASK 0x1f 25 26 #define AP806_CLK_NUM 4 27 28 static struct clk *ap806_clks[AP806_CLK_NUM]; 29 30 static struct clk_onecell_data ap806_clk_data = { 31 .clks = ap806_clks, 32 .clk_num = AP806_CLK_NUM, 33 }; 34 35 static int ap806_syscon_clk_probe(struct platform_device *pdev) 36 { 37 unsigned int freq_mode, cpuclk_freq; 38 const char *name, *fixedclk_name; 39 struct device_node *np = pdev->dev.of_node; 40 struct regmap *regmap; 41 u32 reg; 42 int ret; 43 44 regmap = syscon_node_to_regmap(np); 45 if (IS_ERR(regmap)) { 46 dev_err(&pdev->dev, "cannot get regmap\n"); 47 return PTR_ERR(regmap); 48 } 49 50 ret = regmap_read(regmap, AP806_SAR_REG, ®); 51 if (ret) { 52 dev_err(&pdev->dev, "cannot read from regmap\n"); 53 return ret; 54 } 55 56 freq_mode = reg & AP806_SAR_CLKFREQ_MODE_MASK; 57 switch (freq_mode) { 58 case 0x0 ... 0x5: 59 cpuclk_freq = 2000; 60 break; 61 case 0x6 ... 0xB: 62 cpuclk_freq = 1800; 63 break; 64 case 0xC ... 0x11: 65 cpuclk_freq = 1600; 66 break; 67 case 0x12 ... 0x16: 68 cpuclk_freq = 1400; 69 break; 70 case 0x17 ... 0x19: 71 cpuclk_freq = 1300; 72 break; 73 default: 74 dev_err(&pdev->dev, "invalid SAR value\n"); 75 return -EINVAL; 76 } 77 78 /* Convert to hertz */ 79 cpuclk_freq *= 1000 * 1000; 80 81 /* CPU clocks depend on the Sample At Reset configuration */ 82 of_property_read_string_index(np, "clock-output-names", 83 0, &name); 84 ap806_clks[0] = clk_register_fixed_rate(&pdev->dev, name, NULL, 85 0, cpuclk_freq); 86 if (IS_ERR(ap806_clks[0])) { 87 ret = PTR_ERR(ap806_clks[0]); 88 goto fail0; 89 } 90 91 of_property_read_string_index(np, "clock-output-names", 92 1, &name); 93 ap806_clks[1] = clk_register_fixed_rate(&pdev->dev, name, NULL, 0, 94 cpuclk_freq); 95 if (IS_ERR(ap806_clks[1])) { 96 ret = PTR_ERR(ap806_clks[1]); 97 goto fail1; 98 } 99 100 /* Fixed clock is always 1200 Mhz */ 101 of_property_read_string_index(np, "clock-output-names", 102 2, &fixedclk_name); 103 ap806_clks[2] = clk_register_fixed_rate(&pdev->dev, fixedclk_name, NULL, 104 0, 1200 * 1000 * 1000); 105 if (IS_ERR(ap806_clks[2])) { 106 ret = PTR_ERR(ap806_clks[2]); 107 goto fail2; 108 } 109 110 /* MSS Clock is fixed clock divided by 6 */ 111 of_property_read_string_index(np, "clock-output-names", 112 3, &name); 113 ap806_clks[3] = clk_register_fixed_factor(NULL, name, fixedclk_name, 114 0, 1, 6); 115 if (IS_ERR(ap806_clks[3])) { 116 ret = PTR_ERR(ap806_clks[3]); 117 goto fail3; 118 } 119 120 ret = of_clk_add_provider(np, of_clk_src_onecell_get, &ap806_clk_data); 121 if (ret) 122 goto fail_clk_add; 123 124 return 0; 125 126 fail_clk_add: 127 clk_unregister_fixed_factor(ap806_clks[3]); 128 fail3: 129 clk_unregister_fixed_rate(ap806_clks[2]); 130 fail2: 131 clk_unregister_fixed_rate(ap806_clks[1]); 132 fail1: 133 clk_unregister_fixed_rate(ap806_clks[0]); 134 fail0: 135 return ret; 136 } 137 138 static int ap806_syscon_clk_remove(struct platform_device *pdev) 139 { 140 of_clk_del_provider(pdev->dev.of_node); 141 clk_unregister_fixed_factor(ap806_clks[3]); 142 clk_unregister_fixed_rate(ap806_clks[2]); 143 clk_unregister_fixed_rate(ap806_clks[1]); 144 clk_unregister_fixed_rate(ap806_clks[0]); 145 146 return 0; 147 } 148 149 static const struct of_device_id ap806_syscon_of_match[] = { 150 { .compatible = "marvell,ap806-system-controller", }, 151 { } 152 }; 153 MODULE_DEVICE_TABLE(of, armada8k_pcie_of_match); 154 155 static struct platform_driver ap806_syscon_driver = { 156 .probe = ap806_syscon_clk_probe, 157 .remove = ap806_syscon_clk_remove, 158 .driver = { 159 .name = "marvell-ap806-system-controller", 160 .of_match_table = ap806_syscon_of_match, 161 }, 162 }; 163 164 module_platform_driver(ap806_syscon_driver); 165 166 MODULE_DESCRIPTION("Marvell AP806 System Controller driver"); 167 MODULE_AUTHOR("Thomas Petazzoni <thomas.petazzoni@free-electrons.com>"); 168 MODULE_LICENSE("GPL"); 169