| xj | b04a402 | 2021-11-25 15:01:52 +0800 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 | 
 | 2 | /* | 
 | 3 |  * CS2000  --  CIRRUS LOGIC Fractional-N Clock Synthesizer & Clock Multiplier | 
 | 4 |  * | 
 | 5 |  * Copyright (C) 2015 Renesas Electronics Corporation | 
 | 6 |  * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | 
 | 7 |  */ | 
 | 8 | #include <linux/clk-provider.h> | 
 | 9 | #include <linux/delay.h> | 
 | 10 | #include <linux/clk.h> | 
 | 11 | #include <linux/i2c.h> | 
 | 12 | #include <linux/of_device.h> | 
 | 13 | #include <linux/module.h> | 
 | 14 |  | 
 | 15 | #define CH_MAX 4 | 
 | 16 | #define RATIO_REG_SIZE 4 | 
 | 17 |  | 
 | 18 | #define DEVICE_ID	0x1 | 
 | 19 | #define DEVICE_CTRL	0x2 | 
 | 20 | #define DEVICE_CFG1	0x3 | 
 | 21 | #define DEVICE_CFG2	0x4 | 
 | 22 | #define GLOBAL_CFG	0x5 | 
 | 23 | #define Ratio_Add(x, nth)	(6 + (x * 4) + (nth)) | 
 | 24 | #define Ratio_Val(x, nth)	((x >> (24 - (8 * nth))) & 0xFF) | 
 | 25 | #define Val_Ratio(x, nth)	((x & 0xFF) << (24 - (8 * nth))) | 
 | 26 | #define FUNC_CFG1	0x16 | 
 | 27 | #define FUNC_CFG2	0x17 | 
 | 28 |  | 
 | 29 | /* DEVICE_ID */ | 
 | 30 | #define REVISION_MASK	(0x7) | 
 | 31 | #define REVISION_B2_B3	(0x4) | 
 | 32 | #define REVISION_C1	(0x6) | 
 | 33 |  | 
 | 34 | /* DEVICE_CTRL */ | 
 | 35 | #define PLL_UNLOCK	(1 << 7) | 
 | 36 | #define AUXOUTDIS	(1 << 1) | 
 | 37 | #define CLKOUTDIS	(1 << 0) | 
 | 38 |  | 
 | 39 | /* DEVICE_CFG1 */ | 
 | 40 | #define RSEL(x)		(((x) & 0x3) << 3) | 
 | 41 | #define RSEL_MASK	RSEL(0x3) | 
 | 42 | #define ENDEV1		(0x1) | 
 | 43 |  | 
 | 44 | /* DEVICE_CFG2 */ | 
 | 45 | #define AUTORMOD	(1 << 3) | 
 | 46 | #define LOCKCLK(x)	(((x) & 0x3) << 1) | 
 | 47 | #define LOCKCLK_MASK	LOCKCLK(0x3) | 
 | 48 | #define FRACNSRC_MASK	(1 << 0) | 
 | 49 | #define FRACNSRC_STATIC		(0 << 0) | 
 | 50 | #define FRACNSRC_DYNAMIC	(1 << 1) | 
 | 51 |  | 
 | 52 | /* GLOBAL_CFG */ | 
 | 53 | #define ENDEV2		(0x1) | 
 | 54 |  | 
 | 55 | /* FUNC_CFG1 */ | 
 | 56 | #define CLKSKIPEN	(1 << 7) | 
 | 57 | #define REFCLKDIV(x)	(((x) & 0x3) << 3) | 
 | 58 | #define REFCLKDIV_MASK	REFCLKDIV(0x3) | 
 | 59 |  | 
 | 60 | /* FUNC_CFG2 */ | 
 | 61 | #define LFRATIO_MASK	(1 << 3) | 
 | 62 | #define LFRATIO_20_12	(0 << 3) | 
 | 63 | #define LFRATIO_12_20	(1 << 3) | 
 | 64 |  | 
 | 65 | #define CH_SIZE_ERR(ch)		((ch < 0) || (ch >= CH_MAX)) | 
 | 66 | #define hw_to_priv(_hw)		container_of(_hw, struct cs2000_priv, hw) | 
 | 67 | #define priv_to_client(priv)	(priv->client) | 
 | 68 | #define priv_to_dev(priv)	(&(priv_to_client(priv)->dev)) | 
 | 69 |  | 
 | 70 | #define CLK_IN	0 | 
 | 71 | #define REF_CLK	1 | 
 | 72 | #define CLK_MAX 2 | 
 | 73 |  | 
 | 74 | struct cs2000_priv { | 
 | 75 | 	struct clk_hw hw; | 
 | 76 | 	struct i2c_client *client; | 
 | 77 | 	struct clk *clk_in; | 
 | 78 | 	struct clk *ref_clk; | 
 | 79 |  | 
 | 80 | 	/* suspend/resume */ | 
 | 81 | 	unsigned long saved_rate; | 
 | 82 | 	unsigned long saved_parent_rate; | 
 | 83 | }; | 
 | 84 |  | 
 | 85 | static const struct of_device_id cs2000_of_match[] = { | 
 | 86 | 	{ .compatible = "cirrus,cs2000-cp", }, | 
 | 87 | 	{}, | 
 | 88 | }; | 
 | 89 | MODULE_DEVICE_TABLE(of, cs2000_of_match); | 
 | 90 |  | 
 | 91 | static const struct i2c_device_id cs2000_id[] = { | 
 | 92 | 	{ "cs2000-cp", }, | 
 | 93 | 	{} | 
 | 94 | }; | 
 | 95 | MODULE_DEVICE_TABLE(i2c, cs2000_id); | 
 | 96 |  | 
 | 97 | #define cs2000_read(priv, addr) \ | 
 | 98 | 	i2c_smbus_read_byte_data(priv_to_client(priv), addr) | 
 | 99 | #define cs2000_write(priv, addr, val) \ | 
 | 100 | 	i2c_smbus_write_byte_data(priv_to_client(priv), addr, val) | 
 | 101 |  | 
 | 102 | static int cs2000_bset(struct cs2000_priv *priv, u8 addr, u8 mask, u8 val) | 
 | 103 | { | 
 | 104 | 	s32 data; | 
 | 105 |  | 
 | 106 | 	data = cs2000_read(priv, addr); | 
 | 107 | 	if (data < 0) | 
 | 108 | 		return data; | 
 | 109 |  | 
 | 110 | 	data &= ~mask; | 
 | 111 | 	data |= (val & mask); | 
 | 112 |  | 
 | 113 | 	return cs2000_write(priv, addr, data); | 
 | 114 | } | 
 | 115 |  | 
 | 116 | static int cs2000_enable_dev_config(struct cs2000_priv *priv, bool enable) | 
 | 117 | { | 
 | 118 | 	int ret; | 
 | 119 |  | 
 | 120 | 	ret = cs2000_bset(priv, DEVICE_CFG1, ENDEV1, | 
 | 121 | 			  enable ? ENDEV1 : 0); | 
 | 122 | 	if (ret < 0) | 
 | 123 | 		return ret; | 
 | 124 |  | 
 | 125 | 	ret = cs2000_bset(priv, GLOBAL_CFG,  ENDEV2, | 
 | 126 | 			  enable ? ENDEV2 : 0); | 
 | 127 | 	if (ret < 0) | 
 | 128 | 		return ret; | 
 | 129 |  | 
 | 130 | 	ret = cs2000_bset(priv, FUNC_CFG1, CLKSKIPEN, | 
 | 131 | 			  enable ? CLKSKIPEN : 0); | 
 | 132 | 	if (ret < 0) | 
 | 133 | 		return ret; | 
 | 134 |  | 
 | 135 | 	/* FIXME: for Static ratio mode */ | 
 | 136 | 	ret = cs2000_bset(priv, FUNC_CFG2, LFRATIO_MASK, | 
 | 137 | 			  LFRATIO_12_20); | 
 | 138 | 	if (ret < 0) | 
 | 139 | 		return ret; | 
 | 140 |  | 
 | 141 | 	return 0; | 
 | 142 | } | 
 | 143 |  | 
 | 144 | static int cs2000_clk_in_bound_rate(struct cs2000_priv *priv, | 
 | 145 | 				    u32 rate_in) | 
 | 146 | { | 
 | 147 | 	u32 val; | 
 | 148 |  | 
 | 149 | 	if (rate_in >= 32000000 && rate_in < 56000000) | 
 | 150 | 		val = 0x0; | 
 | 151 | 	else if (rate_in >= 16000000 && rate_in < 28000000) | 
 | 152 | 		val = 0x1; | 
 | 153 | 	else if (rate_in >= 8000000 && rate_in < 14000000) | 
 | 154 | 		val = 0x2; | 
 | 155 | 	else | 
 | 156 | 		return -EINVAL; | 
 | 157 |  | 
 | 158 | 	return cs2000_bset(priv, FUNC_CFG1, | 
 | 159 | 			   REFCLKDIV_MASK, | 
 | 160 | 			   REFCLKDIV(val)); | 
 | 161 | } | 
 | 162 |  | 
 | 163 | static int cs2000_wait_pll_lock(struct cs2000_priv *priv) | 
 | 164 | { | 
 | 165 | 	struct device *dev = priv_to_dev(priv); | 
 | 166 | 	s32 val; | 
 | 167 | 	unsigned int i; | 
 | 168 |  | 
 | 169 | 	for (i = 0; i < 256; i++) { | 
 | 170 | 		val = cs2000_read(priv, DEVICE_CTRL); | 
 | 171 | 		if (val < 0) | 
 | 172 | 			return val; | 
 | 173 | 		if (!(val & PLL_UNLOCK)) | 
 | 174 | 			return 0; | 
 | 175 | 		udelay(1); | 
 | 176 | 	} | 
 | 177 |  | 
 | 178 | 	dev_err(dev, "pll lock failed\n"); | 
 | 179 |  | 
 | 180 | 	return -ETIMEDOUT; | 
 | 181 | } | 
 | 182 |  | 
 | 183 | static int cs2000_clk_out_enable(struct cs2000_priv *priv, bool enable) | 
 | 184 | { | 
 | 185 | 	/* enable both AUX_OUT, CLK_OUT */ | 
 | 186 | 	return cs2000_bset(priv, DEVICE_CTRL, | 
 | 187 | 			   (AUXOUTDIS | CLKOUTDIS), | 
 | 188 | 			   enable ? 0 : | 
 | 189 | 			   (AUXOUTDIS | CLKOUTDIS)); | 
 | 190 | } | 
 | 191 |  | 
 | 192 | static u32 cs2000_rate_to_ratio(u32 rate_in, u32 rate_out) | 
 | 193 | { | 
 | 194 | 	u64 ratio; | 
 | 195 |  | 
 | 196 | 	/* | 
 | 197 | 	 * ratio = rate_out / rate_in * 2^20 | 
 | 198 | 	 * | 
 | 199 | 	 * To avoid over flow, rate_out is u64. | 
 | 200 | 	 * The result should be u32. | 
 | 201 | 	 */ | 
 | 202 | 	ratio = (u64)rate_out << 20; | 
 | 203 | 	do_div(ratio, rate_in); | 
 | 204 |  | 
 | 205 | 	return ratio; | 
 | 206 | } | 
 | 207 |  | 
 | 208 | static unsigned long cs2000_ratio_to_rate(u32 ratio, u32 rate_in) | 
 | 209 | { | 
 | 210 | 	u64 rate_out; | 
 | 211 |  | 
 | 212 | 	/* | 
 | 213 | 	 * ratio = rate_out / rate_in * 2^20 | 
 | 214 | 	 * | 
 | 215 | 	 * To avoid over flow, rate_out is u64. | 
 | 216 | 	 * The result should be u32 or unsigned long. | 
 | 217 | 	 */ | 
 | 218 |  | 
 | 219 | 	rate_out = (u64)ratio * rate_in; | 
 | 220 | 	return rate_out >> 20; | 
 | 221 | } | 
 | 222 |  | 
 | 223 | static int cs2000_ratio_set(struct cs2000_priv *priv, | 
 | 224 | 			    int ch, u32 rate_in, u32 rate_out) | 
 | 225 | { | 
 | 226 | 	u32 val; | 
 | 227 | 	unsigned int i; | 
 | 228 | 	int ret; | 
 | 229 |  | 
 | 230 | 	if (CH_SIZE_ERR(ch)) | 
 | 231 | 		return -EINVAL; | 
 | 232 |  | 
 | 233 | 	val = cs2000_rate_to_ratio(rate_in, rate_out); | 
 | 234 | 	for (i = 0; i < RATIO_REG_SIZE; i++) { | 
 | 235 | 		ret = cs2000_write(priv, | 
 | 236 | 				   Ratio_Add(ch, i), | 
 | 237 | 				   Ratio_Val(val, i)); | 
 | 238 | 		if (ret < 0) | 
 | 239 | 			return ret; | 
 | 240 | 	} | 
 | 241 |  | 
 | 242 | 	return 0; | 
 | 243 | } | 
 | 244 |  | 
 | 245 | static u32 cs2000_ratio_get(struct cs2000_priv *priv, int ch) | 
 | 246 | { | 
 | 247 | 	s32 tmp; | 
 | 248 | 	u32 val; | 
 | 249 | 	unsigned int i; | 
 | 250 |  | 
 | 251 | 	val = 0; | 
 | 252 | 	for (i = 0; i < RATIO_REG_SIZE; i++) { | 
 | 253 | 		tmp = cs2000_read(priv, Ratio_Add(ch, i)); | 
 | 254 | 		if (tmp < 0) | 
 | 255 | 			return 0; | 
 | 256 |  | 
 | 257 | 		val |= Val_Ratio(tmp, i); | 
 | 258 | 	} | 
 | 259 |  | 
 | 260 | 	return val; | 
 | 261 | } | 
 | 262 |  | 
 | 263 | static int cs2000_ratio_select(struct cs2000_priv *priv, int ch) | 
 | 264 | { | 
 | 265 | 	int ret; | 
 | 266 |  | 
 | 267 | 	if (CH_SIZE_ERR(ch)) | 
 | 268 | 		return -EINVAL; | 
 | 269 |  | 
 | 270 | 	/* | 
 | 271 | 	 * FIXME | 
 | 272 | 	 * | 
 | 273 | 	 * this driver supports static ratio mode only at this point. | 
 | 274 | 	 */ | 
 | 275 | 	ret = cs2000_bset(priv, DEVICE_CFG1, RSEL_MASK, RSEL(ch)); | 
 | 276 | 	if (ret < 0) | 
 | 277 | 		return ret; | 
 | 278 |  | 
 | 279 | 	ret = cs2000_bset(priv, DEVICE_CFG2, | 
 | 280 | 			  (AUTORMOD | LOCKCLK_MASK | FRACNSRC_MASK), | 
 | 281 | 			  (LOCKCLK(ch) | FRACNSRC_STATIC)); | 
 | 282 | 	if (ret < 0) | 
 | 283 | 		return ret; | 
 | 284 |  | 
 | 285 | 	return 0; | 
 | 286 | } | 
 | 287 |  | 
 | 288 | static unsigned long cs2000_recalc_rate(struct clk_hw *hw, | 
 | 289 | 					unsigned long parent_rate) | 
 | 290 | { | 
 | 291 | 	struct cs2000_priv *priv = hw_to_priv(hw); | 
 | 292 | 	int ch = 0; /* it uses ch0 only at this point */ | 
 | 293 | 	u32 ratio; | 
 | 294 |  | 
 | 295 | 	ratio = cs2000_ratio_get(priv, ch); | 
 | 296 |  | 
 | 297 | 	return cs2000_ratio_to_rate(ratio, parent_rate); | 
 | 298 | } | 
 | 299 |  | 
 | 300 | static long cs2000_round_rate(struct clk_hw *hw, unsigned long rate, | 
 | 301 | 			      unsigned long *parent_rate) | 
 | 302 | { | 
 | 303 | 	u32 ratio; | 
 | 304 |  | 
 | 305 | 	ratio = cs2000_rate_to_ratio(*parent_rate, rate); | 
 | 306 |  | 
 | 307 | 	return cs2000_ratio_to_rate(ratio, *parent_rate); | 
 | 308 | } | 
 | 309 |  | 
 | 310 | static int __cs2000_set_rate(struct cs2000_priv *priv, int ch, | 
 | 311 | 			     unsigned long rate, unsigned long parent_rate) | 
 | 312 |  | 
 | 313 | { | 
 | 314 | 	int ret; | 
 | 315 |  | 
 | 316 | 	ret = cs2000_clk_in_bound_rate(priv, parent_rate); | 
 | 317 | 	if (ret < 0) | 
 | 318 | 		return ret; | 
 | 319 |  | 
 | 320 | 	ret = cs2000_ratio_set(priv, ch, parent_rate, rate); | 
 | 321 | 	if (ret < 0) | 
 | 322 | 		return ret; | 
 | 323 |  | 
 | 324 | 	ret = cs2000_ratio_select(priv, ch); | 
 | 325 | 	if (ret < 0) | 
 | 326 | 		return ret; | 
 | 327 |  | 
 | 328 | 	priv->saved_rate	= rate; | 
 | 329 | 	priv->saved_parent_rate	= parent_rate; | 
 | 330 |  | 
 | 331 | 	return 0; | 
 | 332 | } | 
 | 333 |  | 
 | 334 | static int cs2000_set_rate(struct clk_hw *hw, | 
 | 335 | 			   unsigned long rate, unsigned long parent_rate) | 
 | 336 | { | 
 | 337 | 	struct cs2000_priv *priv = hw_to_priv(hw); | 
 | 338 | 	int ch = 0; /* it uses ch0 only at this point */ | 
 | 339 |  | 
 | 340 | 	return __cs2000_set_rate(priv, ch, rate, parent_rate); | 
 | 341 | } | 
 | 342 |  | 
 | 343 | static int cs2000_set_saved_rate(struct cs2000_priv *priv) | 
 | 344 | { | 
 | 345 | 	int ch = 0; /* it uses ch0 only at this point */ | 
 | 346 |  | 
 | 347 | 	return __cs2000_set_rate(priv, ch, | 
 | 348 | 				 priv->saved_rate, | 
 | 349 | 				 priv->saved_parent_rate); | 
 | 350 | } | 
 | 351 |  | 
 | 352 | static int cs2000_enable(struct clk_hw *hw) | 
 | 353 | { | 
 | 354 | 	struct cs2000_priv *priv = hw_to_priv(hw); | 
 | 355 | 	int ret; | 
 | 356 |  | 
 | 357 | 	ret = cs2000_enable_dev_config(priv, true); | 
 | 358 | 	if (ret < 0) | 
 | 359 | 		return ret; | 
 | 360 |  | 
 | 361 | 	ret = cs2000_clk_out_enable(priv, true); | 
 | 362 | 	if (ret < 0) | 
 | 363 | 		return ret; | 
 | 364 |  | 
 | 365 | 	ret = cs2000_wait_pll_lock(priv); | 
 | 366 | 	if (ret < 0) | 
 | 367 | 		return ret; | 
 | 368 |  | 
 | 369 | 	return ret; | 
 | 370 | } | 
 | 371 |  | 
 | 372 | static void cs2000_disable(struct clk_hw *hw) | 
 | 373 | { | 
 | 374 | 	struct cs2000_priv *priv = hw_to_priv(hw); | 
 | 375 |  | 
 | 376 | 	cs2000_enable_dev_config(priv, false); | 
 | 377 |  | 
 | 378 | 	cs2000_clk_out_enable(priv, false); | 
 | 379 | } | 
 | 380 |  | 
 | 381 | static u8 cs2000_get_parent(struct clk_hw *hw) | 
 | 382 | { | 
 | 383 | 	/* always return REF_CLK */ | 
 | 384 | 	return REF_CLK; | 
 | 385 | } | 
 | 386 |  | 
 | 387 | static const struct clk_ops cs2000_ops = { | 
 | 388 | 	.get_parent	= cs2000_get_parent, | 
 | 389 | 	.recalc_rate	= cs2000_recalc_rate, | 
 | 390 | 	.round_rate	= cs2000_round_rate, | 
 | 391 | 	.set_rate	= cs2000_set_rate, | 
 | 392 | 	.prepare	= cs2000_enable, | 
 | 393 | 	.unprepare	= cs2000_disable, | 
 | 394 | }; | 
 | 395 |  | 
 | 396 | static int cs2000_clk_get(struct cs2000_priv *priv) | 
 | 397 | { | 
 | 398 | 	struct device *dev = priv_to_dev(priv); | 
 | 399 | 	struct clk *clk_in, *ref_clk; | 
 | 400 |  | 
 | 401 | 	clk_in = devm_clk_get(dev, "clk_in"); | 
 | 402 | 	/* not yet provided */ | 
 | 403 | 	if (IS_ERR(clk_in)) | 
 | 404 | 		return -EPROBE_DEFER; | 
 | 405 |  | 
 | 406 | 	ref_clk = devm_clk_get(dev, "ref_clk"); | 
 | 407 | 	/* not yet provided */ | 
 | 408 | 	if (IS_ERR(ref_clk)) | 
 | 409 | 		return -EPROBE_DEFER; | 
 | 410 |  | 
 | 411 | 	priv->clk_in	= clk_in; | 
 | 412 | 	priv->ref_clk	= ref_clk; | 
 | 413 |  | 
 | 414 | 	return 0; | 
 | 415 | } | 
 | 416 |  | 
 | 417 | static int cs2000_clk_register(struct cs2000_priv *priv) | 
 | 418 | { | 
 | 419 | 	struct device *dev = priv_to_dev(priv); | 
 | 420 | 	struct device_node *np = dev->of_node; | 
 | 421 | 	struct clk_init_data init; | 
 | 422 | 	const char *name = np->name; | 
 | 423 | 	static const char *parent_names[CLK_MAX]; | 
 | 424 | 	int ch = 0; /* it uses ch0 only at this point */ | 
 | 425 | 	int rate; | 
 | 426 | 	int ret; | 
 | 427 |  | 
 | 428 | 	of_property_read_string(np, "clock-output-names", &name); | 
 | 429 |  | 
 | 430 | 	/* | 
 | 431 | 	 * set default rate as 1/1. | 
 | 432 | 	 * otherwise .set_rate which setup ratio | 
 | 433 | 	 * is never called if user requests 1/1 rate | 
 | 434 | 	 */ | 
 | 435 | 	rate = clk_get_rate(priv->ref_clk); | 
 | 436 | 	ret = __cs2000_set_rate(priv, ch, rate, rate); | 
 | 437 | 	if (ret < 0) | 
 | 438 | 		return ret; | 
 | 439 |  | 
 | 440 | 	parent_names[CLK_IN]	= __clk_get_name(priv->clk_in); | 
 | 441 | 	parent_names[REF_CLK]	= __clk_get_name(priv->ref_clk); | 
 | 442 |  | 
 | 443 | 	init.name		= name; | 
 | 444 | 	init.ops		= &cs2000_ops; | 
 | 445 | 	init.flags		= CLK_SET_RATE_GATE; | 
 | 446 | 	init.parent_names	= parent_names; | 
 | 447 | 	init.num_parents	= ARRAY_SIZE(parent_names); | 
 | 448 |  | 
 | 449 | 	priv->hw.init = &init; | 
 | 450 |  | 
 | 451 | 	ret = clk_hw_register(dev, &priv->hw); | 
 | 452 | 	if (ret) | 
 | 453 | 		return ret; | 
 | 454 |  | 
 | 455 | 	ret = of_clk_add_hw_provider(np, of_clk_hw_simple_get, &priv->hw); | 
 | 456 | 	if (ret < 0) { | 
 | 457 | 		clk_hw_unregister(&priv->hw); | 
 | 458 | 		return ret; | 
 | 459 | 	} | 
 | 460 |  | 
 | 461 | 	return 0; | 
 | 462 | } | 
 | 463 |  | 
 | 464 | static int cs2000_version_print(struct cs2000_priv *priv) | 
 | 465 | { | 
 | 466 | 	struct device *dev = priv_to_dev(priv); | 
 | 467 | 	s32 val; | 
 | 468 | 	const char *revision; | 
 | 469 |  | 
 | 470 | 	val = cs2000_read(priv, DEVICE_ID); | 
 | 471 | 	if (val < 0) | 
 | 472 | 		return val; | 
 | 473 |  | 
 | 474 | 	/* CS2000 should be 0x0 */ | 
 | 475 | 	if (val >> 3) | 
 | 476 | 		return -EIO; | 
 | 477 |  | 
 | 478 | 	switch (val & REVISION_MASK) { | 
 | 479 | 	case REVISION_B2_B3: | 
 | 480 | 		revision = "B2 / B3"; | 
 | 481 | 		break; | 
 | 482 | 	case REVISION_C1: | 
 | 483 | 		revision = "C1"; | 
 | 484 | 		break; | 
 | 485 | 	default: | 
 | 486 | 		return -EIO; | 
 | 487 | 	} | 
 | 488 |  | 
 | 489 | 	dev_info(dev, "revision - %s\n", revision); | 
 | 490 |  | 
 | 491 | 	return 0; | 
 | 492 | } | 
 | 493 |  | 
 | 494 | static int cs2000_remove(struct i2c_client *client) | 
 | 495 | { | 
 | 496 | 	struct cs2000_priv *priv = i2c_get_clientdata(client); | 
 | 497 | 	struct device *dev = priv_to_dev(priv); | 
 | 498 | 	struct device_node *np = dev->of_node; | 
 | 499 |  | 
 | 500 | 	of_clk_del_provider(np); | 
 | 501 |  | 
 | 502 | 	clk_hw_unregister(&priv->hw); | 
 | 503 |  | 
 | 504 | 	return 0; | 
 | 505 | } | 
 | 506 |  | 
 | 507 | static int cs2000_probe(struct i2c_client *client, | 
 | 508 | 			const struct i2c_device_id *id) | 
 | 509 | { | 
 | 510 | 	struct cs2000_priv *priv; | 
 | 511 | 	struct device *dev = &client->dev; | 
 | 512 | 	int ret; | 
 | 513 |  | 
 | 514 | 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | 
 | 515 | 	if (!priv) | 
 | 516 | 		return -ENOMEM; | 
 | 517 |  | 
 | 518 | 	priv->client = client; | 
 | 519 | 	i2c_set_clientdata(client, priv); | 
 | 520 |  | 
 | 521 | 	ret = cs2000_clk_get(priv); | 
 | 522 | 	if (ret < 0) | 
 | 523 | 		return ret; | 
 | 524 |  | 
 | 525 | 	ret = cs2000_clk_register(priv); | 
 | 526 | 	if (ret < 0) | 
 | 527 | 		return ret; | 
 | 528 |  | 
 | 529 | 	ret = cs2000_version_print(priv); | 
 | 530 | 	if (ret < 0) | 
 | 531 | 		goto probe_err; | 
 | 532 |  | 
 | 533 | 	return 0; | 
 | 534 |  | 
 | 535 | probe_err: | 
 | 536 | 	cs2000_remove(client); | 
 | 537 |  | 
 | 538 | 	return ret; | 
 | 539 | } | 
 | 540 |  | 
 | 541 | static int __maybe_unused cs2000_resume(struct device *dev) | 
 | 542 | { | 
 | 543 | 	struct cs2000_priv *priv = dev_get_drvdata(dev); | 
 | 544 |  | 
 | 545 | 	return cs2000_set_saved_rate(priv); | 
 | 546 | } | 
 | 547 |  | 
 | 548 | static const struct dev_pm_ops cs2000_pm_ops = { | 
 | 549 | 	SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, cs2000_resume) | 
 | 550 | }; | 
 | 551 |  | 
 | 552 | static struct i2c_driver cs2000_driver = { | 
 | 553 | 	.driver = { | 
 | 554 | 		.name = "cs2000-cp", | 
 | 555 | 		.pm	= &cs2000_pm_ops, | 
 | 556 | 		.of_match_table = cs2000_of_match, | 
 | 557 | 	}, | 
 | 558 | 	.probe		= cs2000_probe, | 
 | 559 | 	.remove		= cs2000_remove, | 
 | 560 | 	.id_table	= cs2000_id, | 
 | 561 | }; | 
 | 562 |  | 
 | 563 | module_i2c_driver(cs2000_driver); | 
 | 564 |  | 
 | 565 | MODULE_DESCRIPTION("CS2000-CP driver"); | 
 | 566 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); | 
 | 567 | MODULE_LICENSE("GPL v2"); |