Commit | Line | Data |
---|---|---|
14556f04 AB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (C) 2012 Sven Schnelle <svens@stackframe.org> | |
7418a119 SS |
3 | |
4 | #include <linux/platform_device.h> | |
5 | #include <linux/module.h> | |
6 | #include <linux/init.h> | |
7 | #include <linux/rtc.h> | |
8 | #include <linux/types.h> | |
9 | #include <linux/bcd.h> | |
7418a119 | 10 | #include <linux/delay.h> |
d890cfc2 | 11 | #include <linux/gpio/consumer.h> |
7418a119 SS |
12 | #include <linux/slab.h> |
13 | ||
14 | #include <linux/io.h> | |
15 | ||
16 | #define DS2404_STATUS_REG 0x200 | |
17 | #define DS2404_CONTROL_REG 0x201 | |
18 | #define DS2404_RTC_REG 0x202 | |
19 | ||
20 | #define DS2404_WRITE_SCRATCHPAD_CMD 0x0f | |
21 | #define DS2404_READ_SCRATCHPAD_CMD 0xaa | |
22 | #define DS2404_COPY_SCRATCHPAD_CMD 0x55 | |
23 | #define DS2404_READ_MEMORY_CMD 0xf0 | |
24 | ||
7418a119 SS |
25 | #define DS2404_RST 0 |
26 | #define DS2404_CLK 1 | |
27 | #define DS2404_DQ 2 | |
28 | ||
7418a119 | 29 | struct ds2404 { |
d890cfc2 LW |
30 | struct device *dev; |
31 | struct gpio_desc *rst_gpiod; | |
32 | struct gpio_desc *clk_gpiod; | |
33 | struct gpio_desc *dq_gpiod; | |
7418a119 SS |
34 | struct rtc_device *rtc; |
35 | }; | |
36 | ||
d890cfc2 | 37 | static int ds2404_gpio_map(struct ds2404 *chip, struct platform_device *pdev) |
7418a119 | 38 | { |
d890cfc2 | 39 | struct device *dev = &pdev->dev; |
7418a119 | 40 | |
d890cfc2 LW |
41 | /* This will de-assert RESET, declare this GPIO as GPIOD_ACTIVE_LOW */ |
42 | chip->rst_gpiod = devm_gpiod_get(dev, "rst", GPIOD_OUT_LOW); | |
43 | if (IS_ERR(chip->rst_gpiod)) | |
44 | return PTR_ERR(chip->rst_gpiod); | |
7418a119 | 45 | |
d890cfc2 LW |
46 | chip->clk_gpiod = devm_gpiod_get(dev, "clk", GPIOD_OUT_HIGH); |
47 | if (IS_ERR(chip->clk_gpiod)) | |
48 | return PTR_ERR(chip->clk_gpiod); | |
7418a119 | 49 | |
d890cfc2 LW |
50 | chip->dq_gpiod = devm_gpiod_get(dev, "dq", GPIOD_ASIS); |
51 | if (IS_ERR(chip->dq_gpiod)) | |
52 | return PTR_ERR(chip->dq_gpiod); | |
7418a119 | 53 | |
d890cfc2 | 54 | return 0; |
7418a119 SS |
55 | } |
56 | ||
d890cfc2 | 57 | static void ds2404_reset(struct ds2404 *chip) |
7418a119 | 58 | { |
d890cfc2 | 59 | gpiod_set_value(chip->rst_gpiod, 1); |
7418a119 | 60 | udelay(1000); |
d890cfc2 LW |
61 | gpiod_set_value(chip->rst_gpiod, 0); |
62 | gpiod_set_value(chip->clk_gpiod, 0); | |
63 | gpiod_direction_output(chip->dq_gpiod, 0); | |
7418a119 SS |
64 | udelay(10); |
65 | } | |
66 | ||
d890cfc2 | 67 | static void ds2404_write_byte(struct ds2404 *chip, u8 byte) |
7418a119 SS |
68 | { |
69 | int i; | |
70 | ||
d890cfc2 | 71 | gpiod_direction_output(chip->dq_gpiod, 1); |
7418a119 | 72 | for (i = 0; i < 8; i++) { |
d890cfc2 | 73 | gpiod_set_value(chip->dq_gpiod, byte & (1 << i)); |
7418a119 | 74 | udelay(10); |
d890cfc2 | 75 | gpiod_set_value(chip->clk_gpiod, 1); |
7418a119 | 76 | udelay(10); |
d890cfc2 | 77 | gpiod_set_value(chip->clk_gpiod, 0); |
7418a119 SS |
78 | udelay(10); |
79 | } | |
80 | } | |
81 | ||
d890cfc2 | 82 | static u8 ds2404_read_byte(struct ds2404 *chip) |
7418a119 SS |
83 | { |
84 | int i; | |
85 | u8 ret = 0; | |
86 | ||
d890cfc2 | 87 | gpiod_direction_input(chip->dq_gpiod); |
7418a119 SS |
88 | |
89 | for (i = 0; i < 8; i++) { | |
d890cfc2 | 90 | gpiod_set_value(chip->clk_gpiod, 0); |
7418a119 | 91 | udelay(10); |
d890cfc2 | 92 | if (gpiod_get_value(chip->dq_gpiod)) |
7418a119 | 93 | ret |= 1 << i; |
d890cfc2 | 94 | gpiod_set_value(chip->clk_gpiod, 1); |
7418a119 SS |
95 | udelay(10); |
96 | } | |
97 | return ret; | |
98 | } | |
99 | ||
d890cfc2 | 100 | static void ds2404_read_memory(struct ds2404 *chip, u16 offset, |
7418a119 SS |
101 | int length, u8 *out) |
102 | { | |
d890cfc2 LW |
103 | ds2404_reset(chip); |
104 | ds2404_write_byte(chip, DS2404_READ_MEMORY_CMD); | |
105 | ds2404_write_byte(chip, offset & 0xff); | |
106 | ds2404_write_byte(chip, (offset >> 8) & 0xff); | |
7418a119 | 107 | while (length--) |
d890cfc2 | 108 | *out++ = ds2404_read_byte(chip); |
7418a119 SS |
109 | } |
110 | ||
d890cfc2 | 111 | static void ds2404_write_memory(struct ds2404 *chip, u16 offset, |
7418a119 SS |
112 | int length, u8 *out) |
113 | { | |
114 | int i; | |
115 | u8 ta01, ta02, es; | |
116 | ||
d890cfc2 LW |
117 | ds2404_reset(chip); |
118 | ds2404_write_byte(chip, DS2404_WRITE_SCRATCHPAD_CMD); | |
119 | ds2404_write_byte(chip, offset & 0xff); | |
120 | ds2404_write_byte(chip, (offset >> 8) & 0xff); | |
7418a119 SS |
121 | |
122 | for (i = 0; i < length; i++) | |
d890cfc2 | 123 | ds2404_write_byte(chip, out[i]); |
7418a119 | 124 | |
d890cfc2 LW |
125 | ds2404_reset(chip); |
126 | ds2404_write_byte(chip, DS2404_READ_SCRATCHPAD_CMD); | |
7418a119 | 127 | |
d890cfc2 LW |
128 | ta01 = ds2404_read_byte(chip); |
129 | ta02 = ds2404_read_byte(chip); | |
130 | es = ds2404_read_byte(chip); | |
7418a119 SS |
131 | |
132 | for (i = 0; i < length; i++) { | |
d890cfc2 LW |
133 | if (out[i] != ds2404_read_byte(chip)) { |
134 | dev_err(chip->dev, "read invalid data\n"); | |
7418a119 SS |
135 | return; |
136 | } | |
137 | } | |
138 | ||
d890cfc2 LW |
139 | ds2404_reset(chip); |
140 | ds2404_write_byte(chip, DS2404_COPY_SCRATCHPAD_CMD); | |
141 | ds2404_write_byte(chip, ta01); | |
142 | ds2404_write_byte(chip, ta02); | |
143 | ds2404_write_byte(chip, es); | |
7418a119 | 144 | |
d890cfc2 | 145 | while (gpiod_get_value(chip->dq_gpiod)) |
7418a119 SS |
146 | ; |
147 | } | |
148 | ||
d890cfc2 | 149 | static void ds2404_enable_osc(struct ds2404 *chip) |
7418a119 SS |
150 | { |
151 | u8 in[1] = { 0x10 }; /* enable oscillator */ | |
d890cfc2 LW |
152 | |
153 | ds2404_write_memory(chip, 0x201, 1, in); | |
7418a119 SS |
154 | } |
155 | ||
156 | static int ds2404_read_time(struct device *dev, struct rtc_time *dt) | |
157 | { | |
d890cfc2 | 158 | struct ds2404 *chip = dev_get_drvdata(dev); |
7418a119 | 159 | unsigned long time = 0; |
8aec4b87 | 160 | __le32 hw_time = 0; |
7418a119 | 161 | |
d890cfc2 | 162 | ds2404_read_memory(chip, 0x203, 4, (u8 *)&hw_time); |
8aec4b87 | 163 | time = le32_to_cpu(hw_time); |
7418a119 | 164 | |
53523216 | 165 | rtc_time64_to_tm(time, dt); |
22652ba7 | 166 | return 0; |
7418a119 SS |
167 | } |
168 | ||
be2b0437 | 169 | static int ds2404_set_time(struct device *dev, struct rtc_time *dt) |
7418a119 | 170 | { |
d890cfc2 | 171 | struct ds2404 *chip = dev_get_drvdata(dev); |
be2b0437 | 172 | u32 time = cpu_to_le32(rtc_tm_to_time64(dt)); |
d890cfc2 | 173 | ds2404_write_memory(chip, 0x203, 4, (u8 *)&time); |
7418a119 SS |
174 | return 0; |
175 | } | |
176 | ||
177 | static const struct rtc_class_ops ds2404_rtc_ops = { | |
178 | .read_time = ds2404_read_time, | |
be2b0437 | 179 | .set_time = ds2404_set_time, |
7418a119 SS |
180 | }; |
181 | ||
182 | static int rtc_probe(struct platform_device *pdev) | |
183 | { | |
7418a119 SS |
184 | struct ds2404 *chip; |
185 | int retval = -EBUSY; | |
186 | ||
2a444cf7 | 187 | chip = devm_kzalloc(&pdev->dev, sizeof(struct ds2404), GFP_KERNEL); |
7418a119 SS |
188 | if (!chip) |
189 | return -ENOMEM; | |
190 | ||
d890cfc2 LW |
191 | chip->dev = &pdev->dev; |
192 | ||
13bfa942 AB |
193 | chip->rtc = devm_rtc_allocate_device(&pdev->dev); |
194 | if (IS_ERR(chip->rtc)) | |
195 | return PTR_ERR(chip->rtc); | |
196 | ||
d890cfc2 | 197 | retval = ds2404_gpio_map(chip, pdev); |
7418a119 | 198 | if (retval) |
d9aa5ca4 AB |
199 | return retval; |
200 | ||
7418a119 SS |
201 | platform_set_drvdata(pdev, chip); |
202 | ||
13bfa942 AB |
203 | chip->rtc->ops = &ds2404_rtc_ops; |
204 | chip->rtc->range_max = U32_MAX; | |
205 | ||
fdcfd854 | 206 | retval = devm_rtc_register_device(chip->rtc); |
13bfa942 | 207 | if (retval) |
d9aa5ca4 | 208 | return retval; |
7418a119 | 209 | |
d890cfc2 | 210 | ds2404_enable_osc(chip); |
7418a119 | 211 | return 0; |
7418a119 SS |
212 | } |
213 | ||
214 | static struct platform_driver rtc_device_driver = { | |
215 | .probe = rtc_probe, | |
7418a119 SS |
216 | .driver = { |
217 | .name = "ds2404", | |
7418a119 SS |
218 | }, |
219 | }; | |
56ae1b8e | 220 | module_platform_driver(rtc_device_driver); |
7418a119 SS |
221 | |
222 | MODULE_DESCRIPTION("DS2404 RTC"); | |
223 | MODULE_AUTHOR("Sven Schnelle"); | |
224 | MODULE_LICENSE("GPL"); | |
225 | MODULE_ALIAS("platform:ds2404"); |