exynos4210_pwm.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /*
  2. * Samsung exynos4210 Pulse Width Modulation Timer
  3. *
  4. * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
  5. * All rights reserved.
  6. *
  7. * Evgeny Voevodin <e.voevodin@samsung.com>
  8. *
  9. * This program is free software; you can redistribute it and/or modify it
  10. * under the terms of the GNU General Public License as published by the
  11. * Free Software Foundation; either version 2 of the License, or (at your
  12. * option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  17. * See the GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License along
  20. * with this program; if not, see <http://www.gnu.org/licenses/>.
  21. */
  22. #include "sysbus.h"
  23. #include "qemu/timer.h"
  24. #include "qemu-common.h"
  25. #include "ptimer.h"
  26. #include "exynos4210.h"
  27. //#define DEBUG_PWM
  28. #ifdef DEBUG_PWM
  29. #define DPRINTF(fmt, ...) \
  30. do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \
  31. ## __VA_ARGS__); } while (0)
  32. #else
  33. #define DPRINTF(fmt, ...) do {} while (0)
  34. #endif
  35. #define EXYNOS4210_PWM_TIMERS_NUM 5
  36. #define EXYNOS4210_PWM_REG_MEM_SIZE 0x50
  37. #define TCFG0 0x0000
  38. #define TCFG1 0x0004
  39. #define TCON 0x0008
  40. #define TCNTB0 0x000C
  41. #define TCMPB0 0x0010
  42. #define TCNTO0 0x0014
  43. #define TCNTB1 0x0018
  44. #define TCMPB1 0x001C
  45. #define TCNTO1 0x0020
  46. #define TCNTB2 0x0024
  47. #define TCMPB2 0x0028
  48. #define TCNTO2 0x002C
  49. #define TCNTB3 0x0030
  50. #define TCMPB3 0x0034
  51. #define TCNTO3 0x0038
  52. #define TCNTB4 0x003C
  53. #define TCNTO4 0x0040
  54. #define TINT_CSTAT 0x0044
  55. #define TCNTB(x) (0xC * (x))
  56. #define TCMPB(x) (0xC * (x) + 1)
  57. #define TCNTO(x) (0xC * (x) + 2)
  58. #define GET_PRESCALER(reg, x) (((reg) & (0xFF << (8 * (x)))) >> 8 * (x))
  59. #define GET_DIVIDER(reg, x) (1 << (((reg) & (0xF << (4 * (x)))) >> (4 * (x))))
  60. /*
  61. * Attention! Timer4 doesn't have OUTPUT_INVERTER,
  62. * so Auto Reload bit is not accessible by macros!
  63. */
  64. #define TCON_TIMER_BASE(x) (((x) ? 1 : 0) * 4 + 4 * (x))
  65. #define TCON_TIMER_START(x) (1 << (TCON_TIMER_BASE(x) + 0))
  66. #define TCON_TIMER_MANUAL_UPD(x) (1 << (TCON_TIMER_BASE(x) + 1))
  67. #define TCON_TIMER_OUTPUT_INV(x) (1 << (TCON_TIMER_BASE(x) + 2))
  68. #define TCON_TIMER_AUTO_RELOAD(x) (1 << (TCON_TIMER_BASE(x) + 3))
  69. #define TCON_TIMER4_AUTO_RELOAD (1 << 22)
  70. #define TINT_CSTAT_STATUS(x) (1 << (5 + (x)))
  71. #define TINT_CSTAT_ENABLE(x) (1 << (x))
  72. /* timer struct */
  73. typedef struct {
  74. uint32_t id; /* timer id */
  75. qemu_irq irq; /* local timer irq */
  76. uint32_t freq; /* timer frequency */
  77. /* use ptimer.c to represent count down timer */
  78. ptimer_state *ptimer; /* timer */
  79. /* registers */
  80. uint32_t reg_tcntb; /* counter register buffer */
  81. uint32_t reg_tcmpb; /* compare register buffer */
  82. struct Exynos4210PWMState *parent;
  83. } Exynos4210PWM;
  84. typedef struct Exynos4210PWMState {
  85. SysBusDevice busdev;
  86. MemoryRegion iomem;
  87. uint32_t reg_tcfg[2];
  88. uint32_t reg_tcon;
  89. uint32_t reg_tint_cstat;
  90. Exynos4210PWM timer[EXYNOS4210_PWM_TIMERS_NUM];
  91. } Exynos4210PWMState;
  92. /*** VMState ***/
  93. static const VMStateDescription vmstate_exynos4210_pwm = {
  94. .name = "exynos4210.pwm.pwm",
  95. .version_id = 1,
  96. .minimum_version_id = 1,
  97. .minimum_version_id_old = 1,
  98. .fields = (VMStateField[]) {
  99. VMSTATE_UINT32(id, Exynos4210PWM),
  100. VMSTATE_UINT32(freq, Exynos4210PWM),
  101. VMSTATE_PTIMER(ptimer, Exynos4210PWM),
  102. VMSTATE_UINT32(reg_tcntb, Exynos4210PWM),
  103. VMSTATE_UINT32(reg_tcmpb, Exynos4210PWM),
  104. VMSTATE_END_OF_LIST()
  105. }
  106. };
  107. static const VMStateDescription vmstate_exynos4210_pwm_state = {
  108. .name = "exynos4210.pwm",
  109. .version_id = 1,
  110. .minimum_version_id = 1,
  111. .minimum_version_id_old = 1,
  112. .fields = (VMStateField[]) {
  113. VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2),
  114. VMSTATE_UINT32(reg_tcon, Exynos4210PWMState),
  115. VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState),
  116. VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState,
  117. EXYNOS4210_PWM_TIMERS_NUM, 0,
  118. vmstate_exynos4210_pwm, Exynos4210PWM),
  119. VMSTATE_END_OF_LIST()
  120. }
  121. };
  122. /*
  123. * PWM update frequency
  124. */
  125. static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id)
  126. {
  127. uint32_t freq;
  128. freq = s->timer[id].freq;
  129. if (id > 1) {
  130. s->timer[id].freq = 24000000 /
  131. ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) *
  132. (GET_DIVIDER(s->reg_tcfg[1], id)));
  133. } else {
  134. s->timer[id].freq = 24000000 /
  135. ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) *
  136. (GET_DIVIDER(s->reg_tcfg[1], id)));
  137. }
  138. if (freq != s->timer[id].freq) {
  139. ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq);
  140. DPRINTF("freq=%dHz\n", s->timer[id].freq);
  141. }
  142. }
  143. /*
  144. * Counter tick handler
  145. */
  146. static void exynos4210_pwm_tick(void *opaque)
  147. {
  148. Exynos4210PWM *s = (Exynos4210PWM *)opaque;
  149. Exynos4210PWMState *p = (Exynos4210PWMState *)s->parent;
  150. uint32_t id = s->id;
  151. bool cmp;
  152. DPRINTF("timer %d tick\n", id);
  153. /* set irq status */
  154. p->reg_tint_cstat |= TINT_CSTAT_STATUS(id);
  155. /* raise IRQ */
  156. if (p->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) {
  157. DPRINTF("timer %d IRQ\n", id);
  158. qemu_irq_raise(p->timer[id].irq);
  159. }
  160. /* reload timer */
  161. if (id != 4) {
  162. cmp = p->reg_tcon & TCON_TIMER_AUTO_RELOAD(id);
  163. } else {
  164. cmp = p->reg_tcon & TCON_TIMER4_AUTO_RELOAD;
  165. }
  166. if (cmp) {
  167. DPRINTF("auto reload timer %d count to %x\n", id,
  168. p->timer[id].reg_tcntb);
  169. ptimer_set_count(p->timer[id].ptimer, p->timer[id].reg_tcntb);
  170. ptimer_run(p->timer[id].ptimer, 1);
  171. } else {
  172. /* stop timer, set status to STOP, see Basic Timer Operation */
  173. p->reg_tcon &= ~TCON_TIMER_START(id);
  174. ptimer_stop(p->timer[id].ptimer);
  175. }
  176. }
  177. /*
  178. * PWM Read
  179. */
  180. static uint64_t exynos4210_pwm_read(void *opaque, hwaddr offset,
  181. unsigned size)
  182. {
  183. Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
  184. uint32_t value = 0;
  185. int index;
  186. switch (offset) {
  187. case TCFG0: case TCFG1:
  188. index = (offset - TCFG0) >> 2;
  189. value = s->reg_tcfg[index];
  190. break;
  191. case TCON:
  192. value = s->reg_tcon;
  193. break;
  194. case TCNTB0: case TCNTB1:
  195. case TCNTB2: case TCNTB3: case TCNTB4:
  196. index = (offset - TCNTB0) / 0xC;
  197. value = s->timer[index].reg_tcntb;
  198. break;
  199. case TCMPB0: case TCMPB1:
  200. case TCMPB2: case TCMPB3:
  201. index = (offset - TCMPB0) / 0xC;
  202. value = s->timer[index].reg_tcmpb;
  203. break;
  204. case TCNTO0: case TCNTO1:
  205. case TCNTO2: case TCNTO3: case TCNTO4:
  206. index = (offset == TCNTO4) ? 4 : (offset - TCNTO0) / 0xC;
  207. value = ptimer_get_count(s->timer[index].ptimer);
  208. break;
  209. case TINT_CSTAT:
  210. value = s->reg_tint_cstat;
  211. break;
  212. default:
  213. fprintf(stderr,
  214. "[exynos4210.pwm: bad read offset " TARGET_FMT_plx "]\n",
  215. offset);
  216. break;
  217. }
  218. return value;
  219. }
  220. /*
  221. * PWM Write
  222. */
  223. static void exynos4210_pwm_write(void *opaque, hwaddr offset,
  224. uint64_t value, unsigned size)
  225. {
  226. Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
  227. int index;
  228. uint32_t new_val;
  229. int i;
  230. switch (offset) {
  231. case TCFG0: case TCFG1:
  232. index = (offset - TCFG0) >> 2;
  233. s->reg_tcfg[index] = value;
  234. /* update timers frequencies */
  235. for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
  236. exynos4210_pwm_update_freq(s, s->timer[i].id);
  237. }
  238. break;
  239. case TCON:
  240. for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
  241. if ((value & TCON_TIMER_MANUAL_UPD(i)) >
  242. (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) {
  243. /*
  244. * TCNTB and TCMPB are loaded into TCNT and TCMP.
  245. * Update timers.
  246. */
  247. /* this will start timer to run, this ok, because
  248. * during processing start bit timer will be stopped
  249. * if needed */
  250. ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb);
  251. DPRINTF("set timer %d count to %x\n", i,
  252. s->timer[i].reg_tcntb);
  253. }
  254. if ((value & TCON_TIMER_START(i)) >
  255. (s->reg_tcon & TCON_TIMER_START(i))) {
  256. /* changed to start */
  257. ptimer_run(s->timer[i].ptimer, 1);
  258. DPRINTF("run timer %d\n", i);
  259. }
  260. if ((value & TCON_TIMER_START(i)) <
  261. (s->reg_tcon & TCON_TIMER_START(i))) {
  262. /* changed to stop */
  263. ptimer_stop(s->timer[i].ptimer);
  264. DPRINTF("stop timer %d\n", i);
  265. }
  266. }
  267. s->reg_tcon = value;
  268. break;
  269. case TCNTB0: case TCNTB1:
  270. case TCNTB2: case TCNTB3: case TCNTB4:
  271. index = (offset - TCNTB0) / 0xC;
  272. s->timer[index].reg_tcntb = value;
  273. break;
  274. case TCMPB0: case TCMPB1:
  275. case TCMPB2: case TCMPB3:
  276. index = (offset - TCMPB0) / 0xC;
  277. s->timer[index].reg_tcmpb = value;
  278. break;
  279. case TINT_CSTAT:
  280. new_val = (s->reg_tint_cstat & 0x3E0) + (0x1F & value);
  281. new_val &= ~(0x3E0 & value);
  282. for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
  283. if ((new_val & TINT_CSTAT_STATUS(i)) <
  284. (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) {
  285. qemu_irq_lower(s->timer[i].irq);
  286. }
  287. }
  288. s->reg_tint_cstat = new_val;
  289. break;
  290. default:
  291. fprintf(stderr,
  292. "[exynos4210.pwm: bad write offset " TARGET_FMT_plx "]\n",
  293. offset);
  294. break;
  295. }
  296. }
  297. /*
  298. * Set default values to timer fields and registers
  299. */
  300. static void exynos4210_pwm_reset(DeviceState *d)
  301. {
  302. Exynos4210PWMState *s = (Exynos4210PWMState *)d;
  303. int i;
  304. s->reg_tcfg[0] = 0x0101;
  305. s->reg_tcfg[1] = 0x0;
  306. s->reg_tcon = 0;
  307. s->reg_tint_cstat = 0;
  308. for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
  309. s->timer[i].reg_tcmpb = 0;
  310. s->timer[i].reg_tcntb = 0;
  311. exynos4210_pwm_update_freq(s, s->timer[i].id);
  312. ptimer_stop(s->timer[i].ptimer);
  313. }
  314. }
  315. static const MemoryRegionOps exynos4210_pwm_ops = {
  316. .read = exynos4210_pwm_read,
  317. .write = exynos4210_pwm_write,
  318. .endianness = DEVICE_NATIVE_ENDIAN,
  319. };
  320. /*
  321. * PWM timer initialization
  322. */
  323. static int exynos4210_pwm_init(SysBusDevice *dev)
  324. {
  325. Exynos4210PWMState *s = FROM_SYSBUS(Exynos4210PWMState, dev);
  326. int i;
  327. QEMUBH *bh;
  328. for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
  329. bh = qemu_bh_new(exynos4210_pwm_tick, &s->timer[i]);
  330. sysbus_init_irq(dev, &s->timer[i].irq);
  331. s->timer[i].ptimer = ptimer_init(bh);
  332. s->timer[i].id = i;
  333. s->timer[i].parent = s;
  334. }
  335. memory_region_init_io(&s->iomem, &exynos4210_pwm_ops, s, "exynos4210-pwm",
  336. EXYNOS4210_PWM_REG_MEM_SIZE);
  337. sysbus_init_mmio(dev, &s->iomem);
  338. return 0;
  339. }
  340. static void exynos4210_pwm_class_init(ObjectClass *klass, void *data)
  341. {
  342. DeviceClass *dc = DEVICE_CLASS(klass);
  343. SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
  344. k->init = exynos4210_pwm_init;
  345. dc->reset = exynos4210_pwm_reset;
  346. dc->vmsd = &vmstate_exynos4210_pwm_state;
  347. }
  348. static const TypeInfo exynos4210_pwm_info = {
  349. .name = "exynos4210.pwm",
  350. .parent = TYPE_SYS_BUS_DEVICE,
  351. .instance_size = sizeof(Exynos4210PWMState),
  352. .class_init = exynos4210_pwm_class_init,
  353. };
  354. static void exynos4210_pwm_register_types(void)
  355. {
  356. type_register_static(&exynos4210_pwm_info);
  357. }
  358. type_init(exynos4210_pwm_register_types)