1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Freescale SAI BCLK as a generic clock driver 4 * 5 * Copyright 2020 Michael Walle <michael@walle.cc> 6 */ 7 8 #include <linux/clk-provider.h> 9 #include <linux/clk.h> 10 #include <linux/err.h> 11 #include <linux/module.h> 12 #include <linux/of.h> 13 #include <linux/of_address.h> 14 #include <linux/platform_device.h> 15 #include <linux/slab.h> 16 17 #define I2S_CSR 0x00 18 #define I2S_CR2 0x08 19 #define I2S_MCR 0x100 20 #define CSR_BCE_BIT 28 21 #define CSR_TE_BIT 31 22 #define CR2_BCD BIT(24) 23 #define CR2_DIV_SHIFT 0 24 #define CR2_DIV_WIDTH 8 25 #define MCR_MOE BIT(30) 26 27 struct fsl_sai_data { 28 unsigned int offset; /* Register offset */ 29 bool have_mclk; /* Have MCLK control */ 30 }; 31 32 struct fsl_sai_clk { 33 const struct fsl_sai_data *data; 34 struct clk_divider bclk_div; 35 struct clk_divider mclk_div; 36 struct clk_gate bclk_gate; 37 struct clk_gate mclk_gate; 38 struct clk_hw *bclk_hw; 39 struct clk_hw *mclk_hw; 40 spinlock_t lock; 41 }; 42 43 static struct clk_hw * 44 fsl_sai_of_clk_get(struct of_phandle_args *clkspec, void *data) 45 { 46 struct fsl_sai_clk *sai_clk = data; 47 48 if (clkspec->args_count == 0) 49 return sai_clk->bclk_hw; 50 51 if (clkspec->args_count == 1) { 52 if (clkspec->args[0] == 0) 53 return sai_clk->bclk_hw; 54 if (sai_clk->data->have_mclk && clkspec->args[0] == 1) 55 return sai_clk->mclk_hw; 56 } 57 58 return ERR_PTR(-EINVAL); 59 } 60 61 static int fsl_sai_clk_register(struct device *dev, void __iomem *base, 62 spinlock_t *lock, struct clk_divider *div, 63 struct clk_gate *gate, struct clk_hw **hw, 64 const int gate_bit, const int dir_bit, 65 const int div_reg, char *name) 66 { 67 const struct fsl_sai_data *data = device_get_match_data(dev); 68 struct clk_parent_data pdata = { .index = 0 }; 69 struct clk_hw *chw; 70 char *cname; 71 72 gate->reg = base + data->offset + I2S_CSR; 73 gate->bit_idx = gate_bit; 74 gate->lock = lock; 75 76 div->reg = base + div_reg; 77 div->shift = CR2_DIV_SHIFT; 78 div->width = CR2_DIV_WIDTH; 79 div->lock = lock; 80 81 cname = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", 82 of_node_full_name(dev->of_node), name); 83 if (!cname) 84 return -ENOMEM; 85 86 /* Set clock direction */ 87 writel(dir_bit, base + div_reg); 88 89 chw = devm_clk_hw_register_composite_pdata(dev, cname, 90 &pdata, 1, NULL, NULL, 91 &div->hw, 92 &clk_divider_ops, 93 &gate->hw, 94 &clk_gate_ops, 95 CLK_SET_RATE_GATE); 96 if (IS_ERR(chw)) 97 return PTR_ERR(chw); 98 99 *hw = chw; 100 101 return 0; 102 } 103 104 static int fsl_sai_clk_probe(struct platform_device *pdev) 105 { 106 struct device *dev = &pdev->dev; 107 const struct fsl_sai_data *data = device_get_match_data(dev); 108 struct fsl_sai_clk *sai_clk; 109 struct clk *clk_bus; 110 void __iomem *base; 111 int ret; 112 113 sai_clk = devm_kzalloc(dev, sizeof(*sai_clk), GFP_KERNEL); 114 if (!sai_clk) 115 return -ENOMEM; 116 117 base = devm_platform_ioremap_resource(pdev, 0); 118 if (IS_ERR(base)) 119 return PTR_ERR(base); 120 121 clk_bus = devm_clk_get_optional_enabled(dev, "bus"); 122 if (IS_ERR(clk_bus)) 123 return PTR_ERR(clk_bus); 124 125 sai_clk->data = data; 126 spin_lock_init(&sai_clk->lock); 127 128 ret = fsl_sai_clk_register(dev, base, &sai_clk->lock, 129 &sai_clk->bclk_div, &sai_clk->bclk_gate, 130 &sai_clk->bclk_hw, CSR_BCE_BIT, CR2_BCD, 131 data->offset + I2S_CR2, "BCLK"); 132 if (ret) 133 return ret; 134 135 if (data->have_mclk) { 136 ret = fsl_sai_clk_register(dev, base, &sai_clk->lock, 137 &sai_clk->mclk_div, 138 &sai_clk->mclk_gate, 139 &sai_clk->mclk_hw, 140 CSR_TE_BIT, MCR_MOE, I2S_MCR, 141 "MCLK"); 142 if (ret) 143 return ret; 144 } 145 146 return devm_of_clk_add_hw_provider(dev, fsl_sai_of_clk_get, sai_clk); 147 } 148 149 static const struct fsl_sai_data fsl_sai_vf610_data = { 150 .offset = 0, 151 .have_mclk = false, 152 }; 153 154 static const struct fsl_sai_data fsl_sai_imx8mq_data = { 155 .offset = 8, 156 .have_mclk = true, 157 }; 158 159 static const struct of_device_id of_fsl_sai_clk_ids[] = { 160 { .compatible = "fsl,vf610-sai-clock", .data = &fsl_sai_vf610_data }, 161 { .compatible = "fsl,imx8mq-sai-clock", .data = &fsl_sai_imx8mq_data }, 162 { } 163 }; 164 MODULE_DEVICE_TABLE(of, of_fsl_sai_clk_ids); 165 166 static struct platform_driver fsl_sai_clk_driver = { 167 .probe = fsl_sai_clk_probe, 168 .driver = { 169 .name = "fsl-sai-clk", 170 .of_match_table = of_fsl_sai_clk_ids, 171 }, 172 }; 173 module_platform_driver(fsl_sai_clk_driver); 174 175 MODULE_DESCRIPTION("Freescale SAI bitclock-as-a-clock driver"); 176 MODULE_AUTHOR("Michael Walle <michael@walle.cc>"); 177 MODULE_ALIAS("platform:fsl-sai-clk"); 178