2
0

exynos4210_pwm.c 12 KB

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