Blame view

drivers/rtc/rtc-s5m.c 19 KB
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
1
  /*
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
2
   * Copyright (c) 2013-2014 Samsung Electronics Co., Ltd
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   *	http://www.samsung.com
   *
   *  Copyright (C) 2013 Google, Inc
   *
   *  This program is free software; you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License as published by
   *  the Free Software Foundation; either version 2 of the License, or
   *  (at your option) any later version.
   *
   *  This program is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   */
a737e835e   Joe Perches   rtc: use more sta...
17
  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
18
19
  #include <linux/module.h>
  #include <linux/i2c.h>
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
20
  #include <linux/bcd.h>
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
21
22
  #include <linux/regmap.h>
  #include <linux/rtc.h>
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
23
24
25
26
  #include <linux/platform_device.h>
  #include <linux/mfd/samsung/core.h>
  #include <linux/mfd/samsung/irq.h>
  #include <linux/mfd/samsung/rtc.h>
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
27
  #include <linux/mfd/samsung/s2mps14.h>
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
28

d73238d4a   Krzysztof Kozlowski   rtc: s5m: limit e...
29
30
  /*
   * Maximum number of retries for checking changes in UDR field
602cb5bba   Krzysztof Kozlowski   mfd/rtc: sec/s5m:...
31
   * of S5M_RTC_UDR_CON register (to limit possible endless loop).
d73238d4a   Krzysztof Kozlowski   rtc: s5m: limit e...
32
33
   *
   * After writing to RTC registers (setting time or alarm) read the UDR field
602cb5bba   Krzysztof Kozlowski   mfd/rtc: sec/s5m:...
34
   * in S5M_RTC_UDR_CON register. UDR is auto-cleared when data have
d73238d4a   Krzysztof Kozlowski   rtc: s5m: limit e...
35
36
37
   * been transferred.
   */
  #define UDR_READ_RETRY_CNT	5
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
38
39
40
41
42
43
44
45
46
47
48
49
  /* Registers used by the driver which are different between chipsets. */
  struct s5m_rtc_reg_config {
  	/* Number of registers used for setting time/alarm0/alarm1 */
  	unsigned int regs_count;
  	/* First register for time, seconds */
  	unsigned int time;
  	/* RTC control register */
  	unsigned int ctrl;
  	/* First register for alarm 0, seconds */
  	unsigned int alarm0;
  	/* First register for alarm 1, seconds */
  	unsigned int alarm1;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
  	/*
  	 * Register for update flag (UDR). Typically setting UDR field to 1
  	 * will enable update of time or alarm register. Then it will be
  	 * auto-cleared after successful update.
  	 */
  	unsigned int rtc_udr_update;
  	/* Mask for UDR field in 'rtc_udr_update' register */
  	unsigned int rtc_udr_mask;
  };
  
  /* Register map for S5M8763 and S5M8767 */
  static const struct s5m_rtc_reg_config s5m_rtc_regs = {
  	.regs_count		= 8,
  	.time			= S5M_RTC_SEC,
  	.ctrl			= S5M_ALARM1_CONF,
  	.alarm0			= S5M_ALARM0_SEC,
  	.alarm1			= S5M_ALARM1_SEC,
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
67
68
69
  	.rtc_udr_update		= S5M_RTC_UDR_CON,
  	.rtc_udr_mask		= S5M_RTC_UDR_MASK,
  };
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
70
71
72
73
74
75
76
77
78
79
  /*
   * Register map for S2MPS14.
   * It may be also suitable for S2MPS11 but this was not tested.
   */
  static const struct s5m_rtc_reg_config s2mps_rtc_regs = {
  	.regs_count		= 7,
  	.time			= S2MPS_RTC_SEC,
  	.ctrl			= S2MPS_RTC_CTRL,
  	.alarm0			= S2MPS_ALARM0_SEC,
  	.alarm1			= S2MPS_ALARM1_SEC,
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
80
81
82
  	.rtc_udr_update		= S2MPS_RTC_UDR_CON,
  	.rtc_udr_mask		= S2MPS_RTC_WUDR_MASK,
  };
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
83
84
  struct s5m_rtc_info {
  	struct device *dev;
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
85
  	struct i2c_client *i2c;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
86
  	struct sec_pmic_dev *s5m87xx;
5ccb7d718   Geert Uytterhoeven   drivers/rtc/rtc-s...
87
  	struct regmap *regmap;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
88
89
  	struct rtc_device *rtc_dev;
  	int irq;
94f919225   Krzysztof Kozlowski   drivers/rtc/rtc-s...
90
  	enum sec_device_type device_type;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
91
  	int rtc_24hr_mode;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
92
  	const struct s5m_rtc_reg_config	*regs;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
93
  };
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
94
95
96
  static const struct regmap_config s5m_rtc_regmap_config = {
  	.reg_bits = 8,
  	.val_bits = 8,
602cb5bba   Krzysztof Kozlowski   mfd/rtc: sec/s5m:...
97
  	.max_register = S5M_RTC_REG_MAX,
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
98
99
100
101
102
103
104
105
  };
  
  static const struct regmap_config s2mps14_rtc_regmap_config = {
  	.reg_bits = 8,
  	.val_bits = 8,
  
  	.max_register = S2MPS_RTC_REG_MAX,
  };
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
  static void s5m8767_data_to_tm(u8 *data, struct rtc_time *tm,
  			       int rtc_24hr_mode)
  {
  	tm->tm_sec = data[RTC_SEC] & 0x7f;
  	tm->tm_min = data[RTC_MIN] & 0x7f;
  	if (rtc_24hr_mode) {
  		tm->tm_hour = data[RTC_HOUR] & 0x1f;
  	} else {
  		tm->tm_hour = data[RTC_HOUR] & 0x0f;
  		if (data[RTC_HOUR] & HOUR_PM_MASK)
  			tm->tm_hour += 12;
  	}
  
  	tm->tm_wday = ffs(data[RTC_WEEKDAY] & 0x7f);
  	tm->tm_mday = data[RTC_DATE] & 0x1f;
  	tm->tm_mon = (data[RTC_MONTH] & 0x0f) - 1;
  	tm->tm_year = (data[RTC_YEAR1] & 0x7f) + 100;
  	tm->tm_yday = 0;
  	tm->tm_isdst = 0;
  }
  
  static int s5m8767_tm_to_data(struct rtc_time *tm, u8 *data)
  {
  	data[RTC_SEC] = tm->tm_sec;
  	data[RTC_MIN] = tm->tm_min;
  
  	if (tm->tm_hour >= 12)
  		data[RTC_HOUR] = tm->tm_hour | HOUR_PM_MASK;
  	else
  		data[RTC_HOUR] = tm->tm_hour & ~HOUR_PM_MASK;
  
  	data[RTC_WEEKDAY] = 1 << tm->tm_wday;
  	data[RTC_DATE] = tm->tm_mday;
  	data[RTC_MONTH] = tm->tm_mon + 1;
  	data[RTC_YEAR1] = tm->tm_year > 100 ? (tm->tm_year - 100) : 0;
  
  	if (tm->tm_year < 100) {
a737e835e   Joe Perches   rtc: use more sta...
143
144
  		pr_err("RTC cannot handle the year %d
  ",
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
145
146
147
148
149
150
  		       1900 + tm->tm_year);
  		return -EINVAL;
  	} else {
  		return 0;
  	}
  }
d73238d4a   Krzysztof Kozlowski   rtc: s5m: limit e...
151
152
153
154
155
156
157
158
159
160
  /*
   * Read RTC_UDR_CON register and wait till UDR field is cleared.
   * This indicates that time/alarm update ended.
   */
  static inline int s5m8767_wait_for_udr_update(struct s5m_rtc_info *info)
  {
  	int ret, retry = UDR_READ_RETRY_CNT;
  	unsigned int data;
  
  	do {
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
161
162
163
  		ret = regmap_read(info->regmap, info->regs->rtc_udr_update,
  				&data);
  	} while (--retry && (data & info->regs->rtc_udr_mask) && !ret);
d73238d4a   Krzysztof Kozlowski   rtc: s5m: limit e...
164
165
166
167
168
169
170
  
  	if (!retry)
  		dev_err(info->dev, "waiting for UDR update, reached max number of retries
  ");
  
  	return ret;
  }
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
171
172
173
174
175
176
177
178
179
180
181
182
  static inline int s5m_check_peding_alarm_interrupt(struct s5m_rtc_info *info,
  		struct rtc_wkalrm *alarm)
  {
  	int ret;
  	unsigned int val;
  
  	switch (info->device_type) {
  	case S5M8767X:
  	case S5M8763X:
  		ret = regmap_read(info->regmap, S5M_RTC_STATUS, &val);
  		val &= S5M_ALARM0_STATUS;
  		break;
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
183
  	case S2MPS14X:
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
184
  	case S2MPS13X:
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
185
186
187
188
  		ret = regmap_read(info->s5m87xx->regmap_pmic, S2MPS14_REG_ST2,
  				&val);
  		val &= S2MPS_ALARM0_STATUS;
  		break;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
189
190
191
192
193
194
195
196
197
198
199
200
201
  	default:
  		return -EINVAL;
  	}
  	if (ret < 0)
  		return ret;
  
  	if (val)
  		alarm->pending = 1;
  	else
  		alarm->pending = 0;
  
  	return 0;
  }
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
202
203
204
205
  static inline int s5m8767_rtc_set_time_reg(struct s5m_rtc_info *info)
  {
  	int ret;
  	unsigned int data;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
206
  	ret = regmap_read(info->regmap, info->regs->rtc_udr_update, &data);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
207
208
209
210
211
  	if (ret < 0) {
  		dev_err(info->dev, "failed to read update reg(%d)
  ", ret);
  		return ret;
  	}
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
212
  	data |= info->regs->rtc_udr_mask;
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
213
214
  	if (info->device_type == S5M8763X || info->device_type == S5M8767X)
  		data |= S5M_RTC_TIME_EN_MASK;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
215

f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
216
  	ret = regmap_write(info->regmap, info->regs->rtc_udr_update, data);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
217
218
219
220
221
  	if (ret < 0) {
  		dev_err(info->dev, "failed to write update reg(%d)
  ", ret);
  		return ret;
  	}
d73238d4a   Krzysztof Kozlowski   rtc: s5m: limit e...
222
  	ret = s5m8767_wait_for_udr_update(info);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
223
224
225
226
227
228
229
230
  
  	return ret;
  }
  
  static inline int s5m8767_rtc_set_alarm_reg(struct s5m_rtc_info *info)
  {
  	int ret;
  	unsigned int data;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
231
  	ret = regmap_read(info->regmap, info->regs->rtc_udr_update, &data);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
232
233
234
235
236
237
  	if (ret < 0) {
  		dev_err(info->dev, "%s: fail to read update reg(%d)
  ",
  			__func__, ret);
  		return ret;
  	}
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
238
  	data |= info->regs->rtc_udr_mask;
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
239
240
241
242
243
244
245
246
  	switch (info->device_type) {
  	case S5M8763X:
  	case S5M8767X:
  		data &= ~S5M_RTC_TIME_EN_MASK;
  		break;
  	case S2MPS14X:
  		data |= S2MPS_RTC_RUDR_MASK;
  		break;
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
247
248
249
  	case S2MPS13X:
  		data |= S2MPS13_RTC_AUDR_MASK;
  		break;
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
250
251
252
  	default:
  		return -EINVAL;
  	}
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
253

f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
254
  	ret = regmap_write(info->regmap, info->regs->rtc_udr_update, data);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
255
256
257
258
259
260
  	if (ret < 0) {
  		dev_err(info->dev, "%s: fail to write update reg(%d)
  ",
  			__func__, ret);
  		return ret;
  	}
d73238d4a   Krzysztof Kozlowski   rtc: s5m: limit e...
261
  	ret = s5m8767_wait_for_udr_update(info);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
262

5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
263
264
265
266
  	/* On S2MPS13 the AUDR is not auto-cleared */
  	if (info->device_type == S2MPS13X)
  		regmap_update_bits(info->regmap, info->regs->rtc_udr_update,
  				   S2MPS13_RTC_AUDR_MASK, 0);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
  	return ret;
  }
  
  static void s5m8763_data_to_tm(u8 *data, struct rtc_time *tm)
  {
  	tm->tm_sec = bcd2bin(data[RTC_SEC]);
  	tm->tm_min = bcd2bin(data[RTC_MIN]);
  
  	if (data[RTC_HOUR] & HOUR_12) {
  		tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x1f);
  		if (data[RTC_HOUR] & HOUR_PM)
  			tm->tm_hour += 12;
  	} else {
  		tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x3f);
  	}
  
  	tm->tm_wday = data[RTC_WEEKDAY] & 0x07;
  	tm->tm_mday = bcd2bin(data[RTC_DATE]);
  	tm->tm_mon = bcd2bin(data[RTC_MONTH]);
  	tm->tm_year = bcd2bin(data[RTC_YEAR1]) + bcd2bin(data[RTC_YEAR2]) * 100;
  	tm->tm_year -= 1900;
  }
  
  static void s5m8763_tm_to_data(struct rtc_time *tm, u8 *data)
  {
  	data[RTC_SEC] = bin2bcd(tm->tm_sec);
  	data[RTC_MIN] = bin2bcd(tm->tm_min);
  	data[RTC_HOUR] = bin2bcd(tm->tm_hour);
  	data[RTC_WEEKDAY] = tm->tm_wday;
  	data[RTC_DATE] = bin2bcd(tm->tm_mday);
  	data[RTC_MONTH] = bin2bcd(tm->tm_mon);
  	data[RTC_YEAR1] = bin2bcd(tm->tm_year % 100);
  	data[RTC_YEAR2] = bin2bcd((tm->tm_year + 1900) / 100);
  }
  
  static int s5m_rtc_read_time(struct device *dev, struct rtc_time *tm)
  {
  	struct s5m_rtc_info *info = dev_get_drvdata(dev);
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
305
  	u8 data[info->regs->regs_count];
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
306
  	int ret;
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
307
  	if (info->device_type == S2MPS14X || info->device_type == S2MPS13X) {
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
308
309
310
311
312
313
314
315
316
317
318
  		ret = regmap_update_bits(info->regmap,
  				info->regs->rtc_udr_update,
  				S2MPS_RTC_RUDR_MASK, S2MPS_RTC_RUDR_MASK);
  		if (ret) {
  			dev_err(dev,
  				"Failed to prepare registers for time reading: %d
  ",
  				ret);
  			return ret;
  		}
  	}
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
319
320
  	ret = regmap_bulk_read(info->regmap, info->regs->time, data,
  			info->regs->regs_count);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
321
322
323
324
325
326
327
328
329
  	if (ret < 0)
  		return ret;
  
  	switch (info->device_type) {
  	case S5M8763X:
  		s5m8763_data_to_tm(data, tm);
  		break;
  
  	case S5M8767X:
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
330
  	case S2MPS14X:
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
331
  	case S2MPS13X:
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
  		s5m8767_data_to_tm(data, tm, info->rtc_24hr_mode);
  		break;
  
  	default:
  		return -EINVAL;
  	}
  
  	dev_dbg(dev, "%s: %d/%d/%d %d:%d:%d(%d)
  ", __func__,
  		1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday,
  		tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
  
  	return rtc_valid_tm(tm);
  }
  
  static int s5m_rtc_set_time(struct device *dev, struct rtc_time *tm)
  {
  	struct s5m_rtc_info *info = dev_get_drvdata(dev);
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
350
  	u8 data[info->regs->regs_count];
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
351
352
353
354
355
356
357
  	int ret = 0;
  
  	switch (info->device_type) {
  	case S5M8763X:
  		s5m8763_tm_to_data(tm, data);
  		break;
  	case S5M8767X:
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
358
  	case S2MPS14X:
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
359
  	case S2MPS13X:
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
360
361
362
363
364
365
366
367
368
369
370
371
372
  		ret = s5m8767_tm_to_data(tm, data);
  		break;
  	default:
  		return -EINVAL;
  	}
  
  	if (ret < 0)
  		return ret;
  
  	dev_dbg(dev, "%s: %d/%d/%d %d:%d:%d(%d)
  ", __func__,
  		1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday,
  		tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
373
374
  	ret = regmap_raw_write(info->regmap, info->regs->time, data,
  			info->regs->regs_count);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
375
376
377
378
379
380
381
382
383
384
385
  	if (ret < 0)
  		return ret;
  
  	ret = s5m8767_rtc_set_time_reg(info);
  
  	return ret;
  }
  
  static int s5m_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
  {
  	struct s5m_rtc_info *info = dev_get_drvdata(dev);
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
386
  	u8 data[info->regs->regs_count];
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
387
388
  	unsigned int val;
  	int ret, i;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
389
390
  	ret = regmap_bulk_read(info->regmap, info->regs->alarm0, data,
  			info->regs->regs_count);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
391
392
393
394
395
396
  	if (ret < 0)
  		return ret;
  
  	switch (info->device_type) {
  	case S5M8763X:
  		s5m8763_data_to_tm(data, &alrm->time);
602cb5bba   Krzysztof Kozlowski   mfd/rtc: sec/s5m:...
397
  		ret = regmap_read(info->regmap, S5M_ALARM0_CONF, &val);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
398
399
400
401
  		if (ret < 0)
  			return ret;
  
  		alrm->enabled = !!val;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
402
403
404
  		break;
  
  	case S5M8767X:
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
405
  	case S2MPS14X:
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
406
  	case S2MPS13X:
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
407
  		s5m8767_data_to_tm(data, &alrm->time, info->rtc_24hr_mode);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
408
  		alrm->enabled = 0;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
409
  		for (i = 0; i < info->regs->regs_count; i++) {
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
410
411
412
413
414
  			if (data[i] & ALARM_ENABLE_MASK) {
  				alrm->enabled = 1;
  				break;
  			}
  		}
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
415
416
417
418
419
  		break;
  
  	default:
  		return -EINVAL;
  	}
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
420
421
422
423
424
425
426
427
  	dev_dbg(dev, "%s: %d/%d/%d %d:%d:%d(%d)
  ", __func__,
  		1900 + alrm->time.tm_year, 1 + alrm->time.tm_mon,
  		alrm->time.tm_mday, alrm->time.tm_hour,
  		alrm->time.tm_min, alrm->time.tm_sec,
  		alrm->time.tm_wday);
  
  	ret = s5m_check_peding_alarm_interrupt(info, alrm);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
428
429
430
431
432
433
  
  	return 0;
  }
  
  static int s5m_rtc_stop_alarm(struct s5m_rtc_info *info)
  {
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
434
  	u8 data[info->regs->regs_count];
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
435
436
  	int ret, i;
  	struct rtc_time tm;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
437
438
  	ret = regmap_bulk_read(info->regmap, info->regs->alarm0, data,
  			info->regs->regs_count);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
439
440
441
442
443
444
445
446
447
448
449
  	if (ret < 0)
  		return ret;
  
  	s5m8767_data_to_tm(data, &tm, info->rtc_24hr_mode);
  	dev_dbg(info->dev, "%s: %d/%d/%d %d:%d:%d(%d)
  ", __func__,
  		1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday,
  		tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
  
  	switch (info->device_type) {
  	case S5M8763X:
602cb5bba   Krzysztof Kozlowski   mfd/rtc: sec/s5m:...
450
  		ret = regmap_write(info->regmap, S5M_ALARM0_CONF, 0);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
451
452
453
  		break;
  
  	case S5M8767X:
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
454
  	case S2MPS14X:
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
455
  	case S2MPS13X:
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
456
  		for (i = 0; i < info->regs->regs_count; i++)
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
457
  			data[i] &= ~ALARM_ENABLE_MASK;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
458
459
  		ret = regmap_raw_write(info->regmap, info->regs->alarm0, data,
  				info->regs->regs_count);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
  		if (ret < 0)
  			return ret;
  
  		ret = s5m8767_rtc_set_alarm_reg(info);
  
  		break;
  
  	default:
  		return -EINVAL;
  	}
  
  	return ret;
  }
  
  static int s5m_rtc_start_alarm(struct s5m_rtc_info *info)
  {
  	int ret;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
477
  	u8 data[info->regs->regs_count];
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
478
479
  	u8 alarm0_conf;
  	struct rtc_time tm;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
480
481
  	ret = regmap_bulk_read(info->regmap, info->regs->alarm0, data,
  			info->regs->regs_count);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
482
483
484
485
486
487
488
489
490
491
492
493
  	if (ret < 0)
  		return ret;
  
  	s5m8767_data_to_tm(data, &tm, info->rtc_24hr_mode);
  	dev_dbg(info->dev, "%s: %d/%d/%d %d:%d:%d(%d)
  ", __func__,
  		1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday,
  		tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
  
  	switch (info->device_type) {
  	case S5M8763X:
  		alarm0_conf = 0x77;
602cb5bba   Krzysztof Kozlowski   mfd/rtc: sec/s5m:...
494
  		ret = regmap_write(info->regmap, S5M_ALARM0_CONF, alarm0_conf);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
495
496
497
  		break;
  
  	case S5M8767X:
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
498
  	case S2MPS14X:
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
499
  	case S2MPS13X:
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
500
501
502
503
504
505
506
507
508
509
  		data[RTC_SEC] |= ALARM_ENABLE_MASK;
  		data[RTC_MIN] |= ALARM_ENABLE_MASK;
  		data[RTC_HOUR] |= ALARM_ENABLE_MASK;
  		data[RTC_WEEKDAY] &= ~ALARM_ENABLE_MASK;
  		if (data[RTC_DATE] & 0x1f)
  			data[RTC_DATE] |= ALARM_ENABLE_MASK;
  		if (data[RTC_MONTH] & 0xf)
  			data[RTC_MONTH] |= ALARM_ENABLE_MASK;
  		if (data[RTC_YEAR1] & 0x7f)
  			data[RTC_YEAR1] |= ALARM_ENABLE_MASK;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
510
511
  		ret = regmap_raw_write(info->regmap, info->regs->alarm0, data,
  				info->regs->regs_count);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
  		if (ret < 0)
  			return ret;
  		ret = s5m8767_rtc_set_alarm_reg(info);
  
  		break;
  
  	default:
  		return -EINVAL;
  	}
  
  	return ret;
  }
  
  static int s5m_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
  {
  	struct s5m_rtc_info *info = dev_get_drvdata(dev);
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
528
  	u8 data[info->regs->regs_count];
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
529
530
531
532
533
534
535
536
  	int ret;
  
  	switch (info->device_type) {
  	case S5M8763X:
  		s5m8763_tm_to_data(&alrm->time, data);
  		break;
  
  	case S5M8767X:
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
537
  	case S2MPS14X:
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
538
  	case S2MPS13X:
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
  		s5m8767_tm_to_data(&alrm->time, data);
  		break;
  
  	default:
  		return -EINVAL;
  	}
  
  	dev_dbg(dev, "%s: %d/%d/%d %d:%d:%d(%d)
  ", __func__,
  		1900 + alrm->time.tm_year, 1 + alrm->time.tm_mon,
  		alrm->time.tm_mday, alrm->time.tm_hour, alrm->time.tm_min,
  		alrm->time.tm_sec, alrm->time.tm_wday);
  
  	ret = s5m_rtc_stop_alarm(info);
  	if (ret < 0)
  		return ret;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
555
556
  	ret = regmap_raw_write(info->regmap, info->regs->alarm0, data,
  			info->regs->regs_count);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
  	if (ret < 0)
  		return ret;
  
  	ret = s5m8767_rtc_set_alarm_reg(info);
  	if (ret < 0)
  		return ret;
  
  	if (alrm->enabled)
  		ret = s5m_rtc_start_alarm(info);
  
  	return ret;
  }
  
  static int s5m_rtc_alarm_irq_enable(struct device *dev,
  				    unsigned int enabled)
  {
  	struct s5m_rtc_info *info = dev_get_drvdata(dev);
  
  	if (enabled)
  		return s5m_rtc_start_alarm(info);
  	else
  		return s5m_rtc_stop_alarm(info);
  }
  
  static irqreturn_t s5m_rtc_alarm_irq(int irq, void *data)
  {
  	struct s5m_rtc_info *info = data;
  
  	rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
  
  	return IRQ_HANDLED;
  }
  
  static const struct rtc_class_ops s5m_rtc_ops = {
  	.read_time = s5m_rtc_read_time,
  	.set_time = s5m_rtc_set_time,
  	.read_alarm = s5m_rtc_read_alarm,
  	.set_alarm = s5m_rtc_set_alarm,
  	.alarm_irq_enable = s5m_rtc_alarm_irq_enable,
  };
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
597
598
599
  static int s5m8767_rtc_init_reg(struct s5m_rtc_info *info)
  {
  	u8 data[2];
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
600
  	int ret;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
601

0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
602
603
604
605
606
607
608
609
610
611
  	switch (info->device_type) {
  	case S5M8763X:
  	case S5M8767X:
  		/* UDR update time. Default of 7.32 ms is too long. */
  		ret = regmap_update_bits(info->regmap, S5M_RTC_UDR_CON,
  				S5M_RTC_UDR_T_MASK, S5M_RTC_UDR_T_450_US);
  		if (ret < 0)
  			dev_err(info->dev, "%s: fail to change UDR time: %d
  ",
  					__func__, ret);
0c5f5d9af   Krzysztof Kozlowski   rtc: s5m: use sho...
612

0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
613
614
615
616
617
618
619
620
  		/* Set RTC control register : Binary mode, 24hour mode */
  		data[0] = (1 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
  		data[1] = (0 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
  
  		ret = regmap_raw_write(info->regmap, S5M_ALARM0_CONF, data, 2);
  		break;
  
  	case S2MPS14X:
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
621
  	case S2MPS13X:
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
622
623
624
625
626
627
628
  		data[0] = (0 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
  		ret = regmap_write(info->regmap, info->regs->ctrl, data[0]);
  		break;
  
  	default:
  		return -EINVAL;
  	}
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
629
630
  
  	info->rtc_24hr_mode = 1;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
631
632
633
634
635
636
  	if (ret < 0) {
  		dev_err(info->dev, "%s: fail to write controlm reg(%d)
  ",
  			__func__, ret);
  		return ret;
  	}
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
637
638
639
640
641
642
643
644
  	return ret;
  }
  
  static int s5m_rtc_probe(struct platform_device *pdev)
  {
  	struct sec_pmic_dev *s5m87xx = dev_get_drvdata(pdev->dev.parent);
  	struct sec_platform_data *pdata = s5m87xx->pdata;
  	struct s5m_rtc_info *info;
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
645
  	const struct regmap_config *regmap_cfg;
a0347f20a   Krzysztof Kozlowski   rtc: s5m: consoli...
646
  	int ret, alarm_irq;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
647
648
649
650
651
652
653
654
655
656
  
  	if (!pdata) {
  		dev_err(pdev->dev.parent, "Platform data not supplied
  ");
  		return -ENODEV;
  	}
  
  	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
  	if (!info)
  		return -ENOMEM;
94f919225   Krzysztof Kozlowski   drivers/rtc/rtc-s...
657
  	switch (platform_get_device_id(pdev)->driver_data) {
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
658
  	case S2MPS14X:
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
659
  	case S2MPS13X:
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
660
  		regmap_cfg = &s2mps14_rtc_regmap_config;
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
661
  		info->regs = &s2mps_rtc_regs;
a0347f20a   Krzysztof Kozlowski   rtc: s5m: consoli...
662
  		alarm_irq = S2MPS14_IRQ_RTCA0;
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
663
664
665
  		break;
  	case S5M8763X:
  		regmap_cfg = &s5m_rtc_regmap_config;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
666
  		info->regs = &s5m_rtc_regs;
a0347f20a   Krzysztof Kozlowski   rtc: s5m: consoli...
667
  		alarm_irq = S5M8763_IRQ_ALARM0;
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
668
669
670
  		break;
  	case S5M8767X:
  		regmap_cfg = &s5m_rtc_regmap_config;
f8b23bbda   Krzysztof Kozlowski   rtc: s5m: support...
671
  		info->regs = &s5m_rtc_regs;
a0347f20a   Krzysztof Kozlowski   rtc: s5m: consoli...
672
  		alarm_irq = S5M8767_IRQ_RTCA1;
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
673
674
  		break;
  	default:
94f919225   Krzysztof Kozlowski   drivers/rtc/rtc-s...
675
676
677
678
  		dev_err(&pdev->dev,
  				"Device type %lu is not supported by RTC driver
  ",
  				platform_get_device_id(pdev)->driver_data);
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
  		return -ENODEV;
  	}
  
  	info->i2c = i2c_new_dummy(s5m87xx->i2c->adapter, RTC_I2C_ADDR);
  	if (!info->i2c) {
  		dev_err(&pdev->dev, "Failed to allocate I2C for RTC
  ");
  		return -ENODEV;
  	}
  
  	info->regmap = devm_regmap_init_i2c(info->i2c, regmap_cfg);
  	if (IS_ERR(info->regmap)) {
  		ret = PTR_ERR(info->regmap);
  		dev_err(&pdev->dev, "Failed to allocate RTC register map: %d
  ",
  				ret);
  		goto err;
  	}
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
697
698
  	info->dev = &pdev->dev;
  	info->s5m87xx = s5m87xx;
94f919225   Krzysztof Kozlowski   drivers/rtc/rtc-s...
699
  	info->device_type = platform_get_device_id(pdev)->driver_data;
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
700

b7d5b9a96   Bartlomiej Zolnierkiewicz   drivers/rtc/rtc-s...
701
702
703
704
705
706
  	if (s5m87xx->irq_data) {
  		info->irq = regmap_irq_get_virq(s5m87xx->irq_data, alarm_irq);
  		if (info->irq <= 0) {
  			ret = -EINVAL;
  			dev_err(&pdev->dev, "Failed to get virtual IRQ %d
  ",
a0347f20a   Krzysztof Kozlowski   rtc: s5m: consoli...
707
  				alarm_irq);
b7d5b9a96   Bartlomiej Zolnierkiewicz   drivers/rtc/rtc-s...
708
709
  			goto err;
  		}
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
710
711
712
713
714
  	}
  
  	platform_set_drvdata(pdev, info);
  
  	ret = s5m8767_rtc_init_reg(info);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
715
716
717
718
  	device_init_wakeup(&pdev->dev, 1);
  
  	info->rtc_dev = devm_rtc_device_register(&pdev->dev, "s5m-rtc",
  						 &s5m_rtc_ops, THIS_MODULE);
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
719
720
721
722
  	if (IS_ERR(info->rtc_dev)) {
  		ret = PTR_ERR(info->rtc_dev);
  		goto err;
  	}
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
723

b7d5b9a96   Bartlomiej Zolnierkiewicz   drivers/rtc/rtc-s...
724
725
726
727
728
  	if (!info->irq) {
  		dev_info(&pdev->dev, "Alarm IRQ not available
  ");
  		return 0;
  	}
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
729
730
731
  	ret = devm_request_threaded_irq(&pdev->dev, info->irq, NULL,
  					s5m_rtc_alarm_irq, 0, "rtc-alarm0",
  					info);
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
732
  	if (ret < 0) {
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
733
734
735
  		dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d
  ",
  			info->irq, ret);
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
736
737
738
739
740
741
742
  		goto err;
  	}
  
  	return 0;
  
  err:
  	i2c_unregister_device(info->i2c);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
743
744
745
  
  	return ret;
  }
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
746
747
748
  static int s5m_rtc_remove(struct platform_device *pdev)
  {
  	struct s5m_rtc_info *info = platform_get_drvdata(pdev);
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
749
750
751
752
  	i2c_unregister_device(info->i2c);
  
  	return 0;
  }
11ba5a1ee   Geert Uytterhoeven   drivers/rtc/rtc-s...
753
  #ifdef CONFIG_PM_SLEEP
222ead7fd   Krzysztof Kozlowski   rtc: s5m: enable ...
754
755
756
757
  static int s5m_rtc_resume(struct device *dev)
  {
  	struct s5m_rtc_info *info = dev_get_drvdata(dev);
  	int ret = 0;
b7d5b9a96   Bartlomiej Zolnierkiewicz   drivers/rtc/rtc-s...
758
  	if (info->irq && device_may_wakeup(dev))
222ead7fd   Krzysztof Kozlowski   rtc: s5m: enable ...
759
760
761
762
763
764
765
766
767
  		ret = disable_irq_wake(info->irq);
  
  	return ret;
  }
  
  static int s5m_rtc_suspend(struct device *dev)
  {
  	struct s5m_rtc_info *info = dev_get_drvdata(dev);
  	int ret = 0;
b7d5b9a96   Bartlomiej Zolnierkiewicz   drivers/rtc/rtc-s...
768
  	if (info->irq && device_may_wakeup(dev))
222ead7fd   Krzysztof Kozlowski   rtc: s5m: enable ...
769
770
771
772
  		ret = enable_irq_wake(info->irq);
  
  	return ret;
  }
11ba5a1ee   Geert Uytterhoeven   drivers/rtc/rtc-s...
773
  #endif /* CONFIG_PM_SLEEP */
222ead7fd   Krzysztof Kozlowski   rtc: s5m: enable ...
774
775
  
  static SIMPLE_DEV_PM_OPS(s5m_rtc_pm_ops, s5m_rtc_suspend, s5m_rtc_resume);
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
776
  static const struct platform_device_id s5m_rtc_id[] = {
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
777
  	{ "s5m-rtc",		S5M8767X },
5281f94ae   Krzysztof Kozlowski   drivers/rtc/rtc-s...
778
  	{ "s2mps13-rtc",	S2MPS13X },
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
779
  	{ "s2mps14-rtc",	S2MPS14X },
45cd15e60   Andrey Ryabinin   drivers/rtc/rtc-s...
780
  	{ },
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
781
782
783
784
785
  };
  
  static struct platform_driver s5m_rtc_driver = {
  	.driver		= {
  		.name	= "s5m-rtc",
222ead7fd   Krzysztof Kozlowski   rtc: s5m: enable ...
786
  		.pm	= &s5m_rtc_pm_ops,
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
787
788
  	},
  	.probe		= s5m_rtc_probe,
e349c910e   Krzysztof Kozlowski   mfd/rtc: s5m: Do ...
789
  	.remove		= s5m_rtc_remove,
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
790
791
792
793
794
795
796
  	.id_table	= s5m_rtc_id,
  };
  
  module_platform_driver(s5m_rtc_driver);
  
  /* Module information */
  MODULE_AUTHOR("Sangbeom Kim <sbkim73@samsung.com>");
0c5deb1ea   Krzysztof Kozlowski   rtc: s5m: add sup...
797
  MODULE_DESCRIPTION("Samsung S5M/S2MPS14 RTC driver");
5bccae6ec   Sangbeom Kim   rtc: s5m-rtc: add...
798
799
  MODULE_LICENSE("GPL");
  MODULE_ALIAS("platform:s5m-rtc");