1 /* 2 * Copyright 2015 Chen-Yu Tsai 3 * 4 * Chen-Yu Tsai <wens@csie.org> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 */ 16 17 #include <linux/clk.h> 18 #include <linux/clk-provider.h> 19 #include <linux/module.h> 20 #include <linux/of.h> 21 #include <linux/of_device.h> 22 #include <linux/reset.h> 23 #include <linux/platform_device.h> 24 #include <linux/reset-controller.h> 25 #include <linux/slab.h> 26 #include <linux/spinlock.h> 27 28 #define SUN9I_MMC_WIDTH 4 29 30 #define SUN9I_MMC_GATE_BIT 16 31 #define SUN9I_MMC_RESET_BIT 18 32 33 struct sun9i_mmc_clk_data { 34 spinlock_t lock; 35 void __iomem *membase; 36 struct clk *clk; 37 struct reset_control *reset; 38 struct clk_onecell_data clk_data; 39 struct reset_controller_dev rcdev; 40 }; 41 42 static int sun9i_mmc_reset_assert(struct reset_controller_dev *rcdev, 43 unsigned long id) 44 { 45 struct sun9i_mmc_clk_data *data = container_of(rcdev, 46 struct sun9i_mmc_clk_data, 47 rcdev); 48 unsigned long flags; 49 void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id; 50 u32 val; 51 52 clk_prepare_enable(data->clk); 53 spin_lock_irqsave(&data->lock, flags); 54 55 val = readl(reg); 56 writel(val & ~BIT(SUN9I_MMC_RESET_BIT), reg); 57 58 spin_unlock_irqrestore(&data->lock, flags); 59 clk_disable_unprepare(data->clk); 60 61 return 0; 62 } 63 64 static int sun9i_mmc_reset_deassert(struct reset_controller_dev *rcdev, 65 unsigned long id) 66 { 67 struct sun9i_mmc_clk_data *data = container_of(rcdev, 68 struct sun9i_mmc_clk_data, 69 rcdev); 70 unsigned long flags; 71 void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id; 72 u32 val; 73 74 clk_prepare_enable(data->clk); 75 spin_lock_irqsave(&data->lock, flags); 76 77 val = readl(reg); 78 writel(val | BIT(SUN9I_MMC_RESET_BIT), reg); 79 80 spin_unlock_irqrestore(&data->lock, flags); 81 clk_disable_unprepare(data->clk); 82 83 return 0; 84 } 85 86 static struct reset_control_ops sun9i_mmc_reset_ops = { 87 .assert = sun9i_mmc_reset_assert, 88 .deassert = sun9i_mmc_reset_deassert, 89 }; 90 91 static int sun9i_a80_mmc_config_clk_probe(struct platform_device *pdev) 92 { 93 struct device_node *np = pdev->dev.of_node; 94 struct sun9i_mmc_clk_data *data; 95 struct clk_onecell_data *clk_data; 96 const char *clk_name = np->name; 97 const char *clk_parent; 98 struct resource *r; 99 int count, i, ret; 100 101 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 102 if (!data) 103 return -ENOMEM; 104 105 spin_lock_init(&data->lock); 106 107 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 108 /* one clock/reset pair per word */ 109 count = DIV_ROUND_UP((r->end - r->start + 1), SUN9I_MMC_WIDTH); 110 data->membase = devm_ioremap_resource(&pdev->dev, r); 111 if (IS_ERR(data->membase)) 112 return PTR_ERR(data->membase); 113 114 clk_data = &data->clk_data; 115 clk_data->clk_num = count; 116 clk_data->clks = devm_kcalloc(&pdev->dev, count, sizeof(struct clk *), 117 GFP_KERNEL); 118 if (!clk_data->clks) 119 return -ENOMEM; 120 121 data->clk = devm_clk_get(&pdev->dev, NULL); 122 if (IS_ERR(data->clk)) { 123 dev_err(&pdev->dev, "Could not get clock\n"); 124 return PTR_ERR(data->clk); 125 } 126 127 data->reset = devm_reset_control_get(&pdev->dev, NULL); 128 if (IS_ERR(data->reset)) { 129 dev_err(&pdev->dev, "Could not get reset control\n"); 130 return PTR_ERR(data->reset); 131 } 132 133 ret = reset_control_deassert(data->reset); 134 if (ret) { 135 dev_err(&pdev->dev, "Reset deassert err %d\n", ret); 136 return ret; 137 } 138 139 clk_parent = __clk_get_name(data->clk); 140 for (i = 0; i < count; i++) { 141 of_property_read_string_index(np, "clock-output-names", 142 i, &clk_name); 143 144 clk_data->clks[i] = clk_register_gate(&pdev->dev, clk_name, 145 clk_parent, 0, 146 data->membase + SUN9I_MMC_WIDTH * i, 147 SUN9I_MMC_GATE_BIT, 0, 148 &data->lock); 149 150 if (IS_ERR(clk_data->clks[i])) { 151 ret = PTR_ERR(clk_data->clks[i]); 152 goto err_clk_register; 153 } 154 } 155 156 ret = of_clk_add_provider(np, of_clk_src_onecell_get, clk_data); 157 if (ret) 158 goto err_clk_provider; 159 160 data->rcdev.owner = THIS_MODULE; 161 data->rcdev.nr_resets = count; 162 data->rcdev.ops = &sun9i_mmc_reset_ops; 163 data->rcdev.of_node = pdev->dev.of_node; 164 165 ret = reset_controller_register(&data->rcdev); 166 if (ret) 167 goto err_rc_reg; 168 169 platform_set_drvdata(pdev, data); 170 171 return 0; 172 173 err_rc_reg: 174 of_clk_del_provider(np); 175 176 err_clk_provider: 177 for (i = 0; i < count; i++) 178 clk_unregister(clk_data->clks[i]); 179 180 err_clk_register: 181 reset_control_assert(data->reset); 182 183 return ret; 184 } 185 186 static int sun9i_a80_mmc_config_clk_remove(struct platform_device *pdev) 187 { 188 struct device_node *np = pdev->dev.of_node; 189 struct sun9i_mmc_clk_data *data = platform_get_drvdata(pdev); 190 struct clk_onecell_data *clk_data = &data->clk_data; 191 int i; 192 193 reset_controller_unregister(&data->rcdev); 194 of_clk_del_provider(np); 195 for (i = 0; i < clk_data->clk_num; i++) 196 clk_unregister(clk_data->clks[i]); 197 198 reset_control_assert(data->reset); 199 200 return 0; 201 } 202 203 static const struct of_device_id sun9i_a80_mmc_config_clk_dt_ids[] = { 204 { .compatible = "allwinner,sun9i-a80-mmc-config-clk" }, 205 { /* sentinel */ } 206 }; 207 MODULE_DEVICE_TABLE(of, sun9i_a80_mmc_config_clk_dt_ids); 208 209 static struct platform_driver sun9i_a80_mmc_config_clk_driver = { 210 .driver = { 211 .name = "sun9i-a80-mmc-config-clk", 212 .of_match_table = sun9i_a80_mmc_config_clk_dt_ids, 213 }, 214 .probe = sun9i_a80_mmc_config_clk_probe, 215 .remove = sun9i_a80_mmc_config_clk_remove, 216 }; 217 module_platform_driver(sun9i_a80_mmc_config_clk_driver); 218 219 MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>"); 220 MODULE_DESCRIPTION("Allwinner A80 MMC clock/reset Driver"); 221 MODULE_LICENSE("GPL v2"); 222