1/*  This file is part of the program psim.
2
3    Copyright (C) 1994-1996, Andrew Cagney <cagney@highland.com.au>
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, see <http://www.gnu.org/licenses/>.
17
18    */
19
20
21#ifndef _HW_NVRAM_C_
22#define _HW_NVRAM_C_
23
24#ifndef STATIC_INLINE_HW_NVRAM
25#define STATIC_INLINE_HW_NVRAM STATIC_INLINE
26#endif
27
28#include "device_table.h"
29
30#include <time.h>
31#include <string.h>
32
33/* DEVICE
34
35
36   nvram - non-volatile memory with clock
37
38
39   DESCRIPTION
40
41
42   This device implements a small byte addressable non-volatile
43   memory.  The top 8 bytes of this memory include a real-time clock.
44
45
46   PROPERTIES
47
48
49   reg = <address> <size> (required)
50
51   Specify the address/size of this device within its parents address
52   space.
53
54
55   timezone = <integer> (optional)
56
57   Adjustment to the hosts current GMT (in seconds) that should be
58   applied when updating the NVRAM's clock.  If no timezone is
59   specified, zero (GMT or UCT) is assumed.
60
61
62   */
63
64typedef struct _hw_nvram_device {
65  uint8_t *memory;
66  unsigned sizeof_memory;
67  time_t host_time;
68  unsigned timezone;
69  /* useful */
70  unsigned addr_year;
71  unsigned addr_month;
72  unsigned addr_date;
73  unsigned addr_day;
74  unsigned addr_hour;
75  unsigned addr_minutes;
76  unsigned addr_seconds;
77  unsigned addr_control;
78} hw_nvram_device;
79
80static void *
81hw_nvram_create(const char *name,
82		const device_unit *unit_address,
83		const char *args)
84{
85  hw_nvram_device *nvram = ZALLOC(hw_nvram_device);
86  return nvram;
87}
88
89typedef struct _hw_nvram_reg_spec {
90  uint32_t base;
91  uint32_t size;
92} hw_nvram_reg_spec;
93
94static void
95hw_nvram_init_address(device *me)
96{
97  hw_nvram_device *nvram = (hw_nvram_device*)device_data(me);
98
99  /* use the generic init code to attach this device to its parent bus */
100  generic_device_init_address(me);
101
102  /* find the first non zero reg property and use that as the device
103     size */
104  if (nvram->sizeof_memory == 0) {
105    reg_property_spec reg;
106    int reg_nr;
107    for (reg_nr = 0;
108	 device_find_reg_array_property(me, "reg", reg_nr, &reg);
109	 reg_nr++) {
110      unsigned attach_size;
111      if (device_size_to_attach_size(device_parent(me),
112				     &reg.size, &attach_size,
113				     me)) {
114	nvram->sizeof_memory = attach_size;
115	break;
116      }
117    }
118    if (nvram->sizeof_memory == 0)
119      device_error(me, "reg property must contain a non-zero phys-addr:size tupple");
120    if (nvram->sizeof_memory < 8)
121      device_error(me, "NVRAM must be at least 8 bytes in size");
122  }
123
124  /* initialize the hw_nvram */
125  if (nvram->memory == NULL) {
126    nvram->memory = zalloc(nvram->sizeof_memory);
127  }
128  else
129    memset(nvram->memory, 0, nvram->sizeof_memory);
130
131  if (device_find_property(me, "timezone") == NULL)
132    nvram->timezone = 0;
133  else
134    nvram->timezone = device_find_integer_property(me, "timezone");
135
136  nvram->addr_year = nvram->sizeof_memory - 1;
137  nvram->addr_month = nvram->sizeof_memory - 2;
138  nvram->addr_date = nvram->sizeof_memory - 3;
139  nvram->addr_day = nvram->sizeof_memory - 4;
140  nvram->addr_hour = nvram->sizeof_memory - 5;
141  nvram->addr_minutes = nvram->sizeof_memory - 6;
142  nvram->addr_seconds = nvram->sizeof_memory - 7;
143  nvram->addr_control = nvram->sizeof_memory - 8;
144
145}
146
147static int
148hw_nvram_bcd(int val)
149{
150  val = val % 100;
151  if (val < 0)
152    val += 100;
153  return ((val / 10) << 4) + (val % 10);
154}
155
156
157/* If reached an update interval and allowed, update the clock within
158   the hw_nvram.  While this function could be implemented using events
159   it isn't on the assumption that the HW_NVRAM will hardly ever be
160   referenced and hence there is little need in keeping the clock
161   continually up-to-date */
162
163static void
164hw_nvram_update_clock(hw_nvram_device *nvram,
165		      cpu *processor)
166{
167  if (!(nvram->memory[nvram->addr_control] & 0xc0)) {
168    time_t host_time = time(NULL);
169    if (nvram->host_time != host_time) {
170      time_t nvtime = host_time + nvram->timezone;
171      struct tm *clock = gmtime(&nvtime);
172      nvram->host_time = host_time;
173      nvram->memory[nvram->addr_year] = hw_nvram_bcd(clock->tm_year);
174      nvram->memory[nvram->addr_month] = hw_nvram_bcd(clock->tm_mon + 1);
175      nvram->memory[nvram->addr_date] = hw_nvram_bcd(clock->tm_mday);
176      nvram->memory[nvram->addr_day] = hw_nvram_bcd(clock->tm_wday + 1);
177      nvram->memory[nvram->addr_hour] = hw_nvram_bcd(clock->tm_hour);
178      nvram->memory[nvram->addr_minutes] = hw_nvram_bcd(clock->tm_min);
179      nvram->memory[nvram->addr_seconds] = hw_nvram_bcd(clock->tm_sec);
180    }
181  }
182}
183
184static void
185hw_nvram_set_clock(hw_nvram_device *nvram, cpu *processor)
186{
187  error ("fixme - how do I set the localtime\n");
188}
189
190static unsigned
191hw_nvram_io_read_buffer(device *me,
192			void *dest,
193			int space,
194			unsigned_word addr,
195			unsigned nr_bytes,
196			cpu *processor,
197			unsigned_word cia)
198{
199  int i;
200  hw_nvram_device *nvram = (hw_nvram_device*)device_data(me);
201  for (i = 0; i < nr_bytes; i++) {
202    unsigned address = (addr + i) % nvram->sizeof_memory;
203    uint8_t data = nvram->memory[address];
204    hw_nvram_update_clock(nvram, processor);
205    ((uint8_t*)dest)[i] = data;
206  }
207  return nr_bytes;
208}
209
210static unsigned
211hw_nvram_io_write_buffer(device *me,
212			 const void *source,
213			 int space,
214			 unsigned_word addr,
215			 unsigned nr_bytes,
216			 cpu *processor,
217			 unsigned_word cia)
218{
219  int i;
220  hw_nvram_device *nvram = (hw_nvram_device*)device_data(me);
221  for (i = 0; i < nr_bytes; i++) {
222    unsigned address = (addr + i) % nvram->sizeof_memory;
223    uint8_t data = ((uint8_t*)source)[i];
224    if (address == nvram->addr_control
225	&& (data & 0x80) == 0
226	&& (nvram->memory[address] & 0x80) == 0x80)
227      hw_nvram_set_clock(nvram, processor);
228    else
229      hw_nvram_update_clock(nvram, processor);
230    nvram->memory[address] = data;
231  }
232  return nr_bytes;
233}
234
235static device_callbacks const hw_nvram_callbacks = {
236  { hw_nvram_init_address, },
237  { NULL, }, /* address */
238  { hw_nvram_io_read_buffer, hw_nvram_io_write_buffer }, /* IO */
239};
240
241const device_descriptor hw_nvram_device_descriptor[] = {
242  { "nvram", hw_nvram_create, &hw_nvram_callbacks },
243  { NULL },
244};
245
246#endif /* _HW_NVRAM_C_ */
247