sh_mobile_meram.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. /*
  2. * SuperH Mobile MERAM Driver for SuperH Mobile LCDC Driver
  3. *
  4. * Copyright (c) 2011 Damian Hobson-Garcia <dhobsong@igel.co.jp>
  5. * Takanari Hayama <taki@igel.co.jp>
  6. *
  7. * This file is subject to the terms and conditions of the GNU General Public
  8. * License. See the file "COPYING" in the main directory of this archive
  9. * for more details.
  10. */
  11. #include <linux/kernel.h>
  12. #include <linux/module.h>
  13. #include <linux/device.h>
  14. #include <linux/pm_runtime.h>
  15. #include <linux/io.h>
  16. #include <linux/slab.h>
  17. #include <linux/platform_device.h>
  18. #include "sh_mobile_meram.h"
  19. /* meram registers */
  20. #define MExxCTL 0x0
  21. #define MExxBSIZE 0x4
  22. #define MExxMNCF 0x8
  23. #define MExxSARA 0x10
  24. #define MExxSARB 0x14
  25. #define MExxSBSIZE 0x18
  26. #define MERAM_MExxCTL_VAL(ctl, next_icb, addr) \
  27. ((ctl) | (((next_icb) & 0x1f) << 11) | (((addr) & 0x7ff) << 16))
  28. #define MERAM_MExxBSIZE_VAL(a, b, c) \
  29. (((a) << 28) | ((b) << 16) | (c))
  30. #define MEVCR1 0x4
  31. #define MEACTS 0x10
  32. #define MEQSEL1 0x40
  33. #define MEQSEL2 0x44
  34. static unsigned long common_regs[] = {
  35. MEVCR1,
  36. MEQSEL1,
  37. MEQSEL2,
  38. };
  39. #define CMN_REGS_SIZE ARRAY_SIZE(common_regs)
  40. static unsigned long icb_regs[] = {
  41. MExxCTL,
  42. MExxBSIZE,
  43. MExxMNCF,
  44. MExxSARA,
  45. MExxSARB,
  46. MExxSBSIZE,
  47. };
  48. #define ICB_REGS_SIZE ARRAY_SIZE(icb_regs)
  49. struct sh_mobile_meram_priv {
  50. void __iomem *base;
  51. struct mutex lock;
  52. unsigned long used_icb;
  53. int used_meram_cache_regions;
  54. unsigned long used_meram_cache[SH_MOBILE_MERAM_ICB_NUM];
  55. unsigned long cmn_saved_regs[CMN_REGS_SIZE];
  56. unsigned long icb_saved_regs[ICB_REGS_SIZE * SH_MOBILE_MERAM_ICB_NUM];
  57. };
  58. /* settings */
  59. #define MERAM_SEC_LINE 15
  60. #define MERAM_LINE_WIDTH 2048
  61. /*
  62. * MERAM/ICB access functions
  63. */
  64. #define MERAM_ICB_OFFSET(base, idx, off) \
  65. ((base) + (0x400 + ((idx) * 0x20) + (off)))
  66. static inline void meram_write_icb(void __iomem *base, int idx, int off,
  67. unsigned long val)
  68. {
  69. iowrite32(val, MERAM_ICB_OFFSET(base, idx, off));
  70. }
  71. static inline unsigned long meram_read_icb(void __iomem *base, int idx, int off)
  72. {
  73. return ioread32(MERAM_ICB_OFFSET(base, idx, off));
  74. }
  75. static inline void meram_write_reg(void __iomem *base, int off,
  76. unsigned long val)
  77. {
  78. iowrite32(val, base + off);
  79. }
  80. static inline unsigned long meram_read_reg(void __iomem *base, int off)
  81. {
  82. return ioread32(base + off);
  83. }
  84. /*
  85. * register ICB
  86. */
  87. #define MERAM_CACHE_START(p) ((p) >> 16)
  88. #define MERAM_CACHE_END(p) ((p) & 0xffff)
  89. #define MERAM_CACHE_SET(o, s) ((((o) & 0xffff) << 16) | \
  90. (((o) + (s) - 1) & 0xffff))
  91. /*
  92. * check if there's no overlaps in MERAM allocation.
  93. */
  94. static inline int meram_check_overlap(struct sh_mobile_meram_priv *priv,
  95. struct sh_mobile_meram_icb *new)
  96. {
  97. int i;
  98. int used_start, used_end, meram_start, meram_end;
  99. /* valid ICB? */
  100. if (new->marker_icb & ~0x1f || new->cache_icb & ~0x1f)
  101. return 1;
  102. if (test_bit(new->marker_icb, &priv->used_icb) ||
  103. test_bit(new->cache_icb, &priv->used_icb))
  104. return 1;
  105. for (i = 0; i < priv->used_meram_cache_regions; i++) {
  106. used_start = MERAM_CACHE_START(priv->used_meram_cache[i]);
  107. used_end = MERAM_CACHE_END(priv->used_meram_cache[i]);
  108. meram_start = new->meram_offset;
  109. meram_end = new->meram_offset + new->meram_size;
  110. if ((meram_start >= used_start && meram_start < used_end) ||
  111. (meram_end > used_start && meram_end < used_end))
  112. return 1;
  113. }
  114. return 0;
  115. }
  116. /*
  117. * mark the specified ICB as used
  118. */
  119. static inline void meram_mark(struct sh_mobile_meram_priv *priv,
  120. struct sh_mobile_meram_icb *new)
  121. {
  122. int n;
  123. if (new->marker_icb < 0 || new->cache_icb < 0)
  124. return;
  125. __set_bit(new->marker_icb, &priv->used_icb);
  126. __set_bit(new->cache_icb, &priv->used_icb);
  127. n = priv->used_meram_cache_regions;
  128. priv->used_meram_cache[n] = MERAM_CACHE_SET(new->meram_offset,
  129. new->meram_size);
  130. priv->used_meram_cache_regions++;
  131. }
  132. /*
  133. * unmark the specified ICB as used
  134. */
  135. static inline void meram_unmark(struct sh_mobile_meram_priv *priv,
  136. struct sh_mobile_meram_icb *icb)
  137. {
  138. int i;
  139. unsigned long pattern;
  140. if (icb->marker_icb < 0 || icb->cache_icb < 0)
  141. return;
  142. __clear_bit(icb->marker_icb, &priv->used_icb);
  143. __clear_bit(icb->cache_icb, &priv->used_icb);
  144. pattern = MERAM_CACHE_SET(icb->meram_offset, icb->meram_size);
  145. for (i = 0; i < priv->used_meram_cache_regions; i++) {
  146. if (priv->used_meram_cache[i] == pattern) {
  147. while (i < priv->used_meram_cache_regions - 1) {
  148. priv->used_meram_cache[i] =
  149. priv->used_meram_cache[i + 1] ;
  150. i++;
  151. }
  152. priv->used_meram_cache[i] = 0;
  153. priv->used_meram_cache_regions--;
  154. break;
  155. }
  156. }
  157. }
  158. /*
  159. * is this a YCbCr(NV12, NV16 or NV24) colorspace
  160. */
  161. static inline int is_nvcolor(int cspace)
  162. {
  163. if (cspace == SH_MOBILE_MERAM_PF_NV ||
  164. cspace == SH_MOBILE_MERAM_PF_NV24)
  165. return 1;
  166. return 0;
  167. }
  168. /*
  169. * set the next address to fetch
  170. */
  171. static inline void meram_set_next_addr(struct sh_mobile_meram_priv *priv,
  172. struct sh_mobile_meram_cfg *cfg,
  173. unsigned long base_addr_y,
  174. unsigned long base_addr_c)
  175. {
  176. unsigned long target;
  177. target = (cfg->current_reg) ? MExxSARA : MExxSARB;
  178. cfg->current_reg ^= 1;
  179. /* set the next address to fetch */
  180. meram_write_icb(priv->base, cfg->icb[0].cache_icb, target,
  181. base_addr_y);
  182. meram_write_icb(priv->base, cfg->icb[0].marker_icb, target,
  183. base_addr_y + cfg->icb[0].cache_unit);
  184. if (is_nvcolor(cfg->pixelformat)) {
  185. meram_write_icb(priv->base, cfg->icb[1].cache_icb, target,
  186. base_addr_c);
  187. meram_write_icb(priv->base, cfg->icb[1].marker_icb, target,
  188. base_addr_c + cfg->icb[1].cache_unit);
  189. }
  190. }
  191. /*
  192. * get the next ICB address
  193. */
  194. static inline void meram_get_next_icb_addr(struct sh_mobile_meram_info *pdata,
  195. struct sh_mobile_meram_cfg *cfg,
  196. unsigned long *icb_addr_y,
  197. unsigned long *icb_addr_c)
  198. {
  199. unsigned long icb_offset;
  200. if (pdata->addr_mode == SH_MOBILE_MERAM_MODE0)
  201. icb_offset = 0x80000000 | (cfg->current_reg << 29);
  202. else
  203. icb_offset = 0xc0000000 | (cfg->current_reg << 23);
  204. *icb_addr_y = icb_offset | (cfg->icb[0].marker_icb << 24);
  205. if (is_nvcolor(cfg->pixelformat))
  206. *icb_addr_c = icb_offset | (cfg->icb[1].marker_icb << 24);
  207. }
  208. #define MERAM_CALC_BYTECOUNT(x, y) \
  209. (((x) * (y) + (MERAM_LINE_WIDTH - 1)) & ~(MERAM_LINE_WIDTH - 1))
  210. /*
  211. * initialize MERAM
  212. */
  213. static int meram_init(struct sh_mobile_meram_priv *priv,
  214. struct sh_mobile_meram_icb *icb,
  215. int xres, int yres, int *out_pitch)
  216. {
  217. unsigned long total_byte_count = MERAM_CALC_BYTECOUNT(xres, yres);
  218. unsigned long bnm;
  219. int lcdc_pitch, xpitch, line_cnt;
  220. int save_lines;
  221. /* adjust pitch to 1024, 2048, 4096 or 8192 */
  222. lcdc_pitch = (xres - 1) | 1023;
  223. lcdc_pitch = lcdc_pitch | (lcdc_pitch >> 1);
  224. lcdc_pitch = lcdc_pitch | (lcdc_pitch >> 2);
  225. lcdc_pitch += 1;
  226. /* derive settings */
  227. if (lcdc_pitch == 8192 && yres >= 1024) {
  228. lcdc_pitch = xpitch = MERAM_LINE_WIDTH;
  229. line_cnt = total_byte_count >> 11;
  230. *out_pitch = xres;
  231. save_lines = (icb->meram_size / 16 / MERAM_SEC_LINE);
  232. save_lines *= MERAM_SEC_LINE;
  233. } else {
  234. xpitch = xres;
  235. line_cnt = yres;
  236. *out_pitch = lcdc_pitch;
  237. save_lines = icb->meram_size / (lcdc_pitch >> 10) / 2;
  238. save_lines &= 0xff;
  239. }
  240. bnm = (save_lines - 1) << 16;
  241. /* TODO: we better to check if we have enough MERAM buffer size */
  242. /* set up ICB */
  243. meram_write_icb(priv->base, icb->cache_icb, MExxBSIZE,
  244. MERAM_MExxBSIZE_VAL(0x0, line_cnt - 1, xpitch - 1));
  245. meram_write_icb(priv->base, icb->marker_icb, MExxBSIZE,
  246. MERAM_MExxBSIZE_VAL(0xf, line_cnt - 1, xpitch - 1));
  247. meram_write_icb(priv->base, icb->cache_icb, MExxMNCF, bnm);
  248. meram_write_icb(priv->base, icb->marker_icb, MExxMNCF, bnm);
  249. meram_write_icb(priv->base, icb->cache_icb, MExxSBSIZE, xpitch);
  250. meram_write_icb(priv->base, icb->marker_icb, MExxSBSIZE, xpitch);
  251. /* save a cache unit size */
  252. icb->cache_unit = xres * save_lines;
  253. /*
  254. * Set MERAM for framebuffer
  255. *
  256. * 0x70f: WD = 0x3, WS=0x1, CM=0x1, MD=FB mode
  257. * we also chain the cache_icb and the marker_icb.
  258. * we also split the allocated MERAM buffer between two ICBs.
  259. */
  260. meram_write_icb(priv->base, icb->cache_icb, MExxCTL,
  261. MERAM_MExxCTL_VAL(0x70f, icb->marker_icb,
  262. icb->meram_offset));
  263. meram_write_icb(priv->base, icb->marker_icb, MExxCTL,
  264. MERAM_MExxCTL_VAL(0x70f, icb->cache_icb,
  265. icb->meram_offset +
  266. icb->meram_size / 2));
  267. return 0;
  268. }
  269. static void meram_deinit(struct sh_mobile_meram_priv *priv,
  270. struct sh_mobile_meram_icb *icb)
  271. {
  272. /* disable ICB */
  273. meram_write_icb(priv->base, icb->cache_icb, MExxCTL, 0);
  274. meram_write_icb(priv->base, icb->marker_icb, MExxCTL, 0);
  275. icb->cache_unit = 0;
  276. }
  277. /*
  278. * register the ICB
  279. */
  280. static int sh_mobile_meram_register(struct sh_mobile_meram_info *pdata,
  281. struct sh_mobile_meram_cfg *cfg,
  282. int xres, int yres, int pixelformat,
  283. unsigned long base_addr_y,
  284. unsigned long base_addr_c,
  285. unsigned long *icb_addr_y,
  286. unsigned long *icb_addr_c,
  287. int *pitch)
  288. {
  289. struct platform_device *pdev;
  290. struct sh_mobile_meram_priv *priv;
  291. int n, out_pitch;
  292. int error = 0;
  293. if (!pdata || !pdata->priv || !pdata->pdev || !cfg)
  294. return -EINVAL;
  295. if (pixelformat != SH_MOBILE_MERAM_PF_NV &&
  296. pixelformat != SH_MOBILE_MERAM_PF_NV24 &&
  297. pixelformat != SH_MOBILE_MERAM_PF_RGB)
  298. return -EINVAL;
  299. priv = pdata->priv;
  300. pdev = pdata->pdev;
  301. dev_dbg(&pdev->dev, "registering %dx%d (%s) (y=%08lx, c=%08lx)",
  302. xres, yres, (!pixelformat) ? "yuv" : "rgb",
  303. base_addr_y, base_addr_c);
  304. mutex_lock(&priv->lock);
  305. /* we can't handle wider than 8192px */
  306. if (xres > 8192) {
  307. dev_err(&pdev->dev, "width exceeding the limit (> 8192).");
  308. error = -EINVAL;
  309. goto err;
  310. }
  311. if (priv->used_meram_cache_regions + 2 > SH_MOBILE_MERAM_ICB_NUM) {
  312. dev_err(&pdev->dev, "no more ICB available.");
  313. error = -EINVAL;
  314. goto err;
  315. }
  316. /* do we have at least one ICB config? */
  317. if (cfg->icb[0].marker_icb < 0 || cfg->icb[0].cache_icb < 0) {
  318. dev_err(&pdev->dev, "at least one ICB is required.");
  319. error = -EINVAL;
  320. goto err;
  321. }
  322. /* make sure that there's no overlaps */
  323. if (meram_check_overlap(priv, &cfg->icb[0])) {
  324. dev_err(&pdev->dev, "conflicting config detected.");
  325. error = -EINVAL;
  326. goto err;
  327. }
  328. n = 1;
  329. /* do the same if we have the second ICB set */
  330. if (cfg->icb[1].marker_icb >= 0 && cfg->icb[1].cache_icb >= 0) {
  331. if (meram_check_overlap(priv, &cfg->icb[1])) {
  332. dev_err(&pdev->dev, "conflicting config detected.");
  333. error = -EINVAL;
  334. goto err;
  335. }
  336. n = 2;
  337. }
  338. if (is_nvcolor(pixelformat) && n != 2) {
  339. dev_err(&pdev->dev, "requires two ICB sets for planar Y/C.");
  340. error = -EINVAL;
  341. goto err;
  342. }
  343. /* we now register the ICB */
  344. cfg->pixelformat = pixelformat;
  345. meram_mark(priv, &cfg->icb[0]);
  346. if (is_nvcolor(pixelformat))
  347. meram_mark(priv, &cfg->icb[1]);
  348. /* initialize MERAM */
  349. meram_init(priv, &cfg->icb[0], xres, yres, &out_pitch);
  350. *pitch = out_pitch;
  351. if (pixelformat == SH_MOBILE_MERAM_PF_NV)
  352. meram_init(priv, &cfg->icb[1], xres, (yres + 1) / 2,
  353. &out_pitch);
  354. else if (pixelformat == SH_MOBILE_MERAM_PF_NV24)
  355. meram_init(priv, &cfg->icb[1], 2 * xres, (yres + 1) / 2,
  356. &out_pitch);
  357. cfg->current_reg = 1;
  358. meram_set_next_addr(priv, cfg, base_addr_y, base_addr_c);
  359. meram_get_next_icb_addr(pdata, cfg, icb_addr_y, icb_addr_c);
  360. dev_dbg(&pdev->dev, "registered - can access via y=%08lx, c=%08lx",
  361. *icb_addr_y, *icb_addr_c);
  362. err:
  363. mutex_unlock(&priv->lock);
  364. return error;
  365. }
  366. static int sh_mobile_meram_unregister(struct sh_mobile_meram_info *pdata,
  367. struct sh_mobile_meram_cfg *cfg)
  368. {
  369. struct sh_mobile_meram_priv *priv;
  370. if (!pdata || !pdata->priv || !cfg)
  371. return -EINVAL;
  372. priv = pdata->priv;
  373. mutex_lock(&priv->lock);
  374. /* deinit & unmark */
  375. if (is_nvcolor(cfg->pixelformat)) {
  376. meram_deinit(priv, &cfg->icb[1]);
  377. meram_unmark(priv, &cfg->icb[1]);
  378. }
  379. meram_deinit(priv, &cfg->icb[0]);
  380. meram_unmark(priv, &cfg->icb[0]);
  381. mutex_unlock(&priv->lock);
  382. return 0;
  383. }
  384. static int sh_mobile_meram_update(struct sh_mobile_meram_info *pdata,
  385. struct sh_mobile_meram_cfg *cfg,
  386. unsigned long base_addr_y,
  387. unsigned long base_addr_c,
  388. unsigned long *icb_addr_y,
  389. unsigned long *icb_addr_c)
  390. {
  391. struct sh_mobile_meram_priv *priv;
  392. if (!pdata || !pdata->priv || !cfg)
  393. return -EINVAL;
  394. priv = pdata->priv;
  395. mutex_lock(&priv->lock);
  396. meram_set_next_addr(priv, cfg, base_addr_y, base_addr_c);
  397. meram_get_next_icb_addr(pdata, cfg, icb_addr_y, icb_addr_c);
  398. mutex_unlock(&priv->lock);
  399. return 0;
  400. }
  401. static int sh_mobile_meram_runtime_suspend(struct device *dev)
  402. {
  403. struct platform_device *pdev = to_platform_device(dev);
  404. struct sh_mobile_meram_priv *priv = platform_get_drvdata(pdev);
  405. int k, j;
  406. for (k = 0; k < CMN_REGS_SIZE; k++)
  407. priv->cmn_saved_regs[k] = meram_read_reg(priv->base,
  408. common_regs[k]);
  409. for (j = 0; j < 32; j++) {
  410. if (!test_bit(j, &priv->used_icb))
  411. continue;
  412. for (k = 0; k < ICB_REGS_SIZE; k++) {
  413. priv->icb_saved_regs[j * ICB_REGS_SIZE + k] =
  414. meram_read_icb(priv->base, j, icb_regs[k]);
  415. /* Reset ICB on resume */
  416. if (icb_regs[k] == MExxCTL)
  417. priv->icb_saved_regs[j * ICB_REGS_SIZE + k] =
  418. 0x70;
  419. }
  420. }
  421. return 0;
  422. }
  423. static int sh_mobile_meram_runtime_resume(struct device *dev)
  424. {
  425. struct platform_device *pdev = to_platform_device(dev);
  426. struct sh_mobile_meram_priv *priv = platform_get_drvdata(pdev);
  427. int k, j;
  428. for (j = 0; j < 32; j++) {
  429. if (!test_bit(j, &priv->used_icb))
  430. continue;
  431. for (k = 0; k < ICB_REGS_SIZE; k++) {
  432. meram_write_icb(priv->base, j, icb_regs[k],
  433. priv->icb_saved_regs[j * ICB_REGS_SIZE + k]);
  434. }
  435. }
  436. for (k = 0; k < CMN_REGS_SIZE; k++)
  437. meram_write_reg(priv->base, common_regs[k],
  438. priv->cmn_saved_regs[k]);
  439. return 0;
  440. }
  441. static const struct dev_pm_ops sh_mobile_meram_dev_pm_ops = {
  442. .runtime_suspend = sh_mobile_meram_runtime_suspend,
  443. .runtime_resume = sh_mobile_meram_runtime_resume,
  444. };
  445. static struct sh_mobile_meram_ops sh_mobile_meram_ops = {
  446. .module = THIS_MODULE,
  447. .meram_register = sh_mobile_meram_register,
  448. .meram_unregister = sh_mobile_meram_unregister,
  449. .meram_update = sh_mobile_meram_update,
  450. };
  451. /*
  452. * initialize MERAM
  453. */
  454. static int sh_mobile_meram_remove(struct platform_device *pdev);
  455. static int __devinit sh_mobile_meram_probe(struct platform_device *pdev)
  456. {
  457. struct sh_mobile_meram_priv *priv;
  458. struct sh_mobile_meram_info *pdata = pdev->dev.platform_data;
  459. struct resource *res;
  460. int error;
  461. if (!pdata) {
  462. dev_err(&pdev->dev, "no platform data defined\n");
  463. return -EINVAL;
  464. }
  465. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  466. if (!res) {
  467. dev_err(&pdev->dev, "cannot get platform resources\n");
  468. return -ENOENT;
  469. }
  470. priv = kzalloc(sizeof(*priv), GFP_KERNEL);
  471. if (!priv) {
  472. dev_err(&pdev->dev, "cannot allocate device data\n");
  473. return -ENOMEM;
  474. }
  475. platform_set_drvdata(pdev, priv);
  476. /* initialize private data */
  477. mutex_init(&priv->lock);
  478. priv->base = ioremap_nocache(res->start, resource_size(res));
  479. if (!priv->base) {
  480. dev_err(&pdev->dev, "ioremap failed\n");
  481. error = -EFAULT;
  482. goto err;
  483. }
  484. pdata->ops = &sh_mobile_meram_ops;
  485. pdata->priv = priv;
  486. pdata->pdev = pdev;
  487. /* initialize ICB addressing mode */
  488. if (pdata->addr_mode == SH_MOBILE_MERAM_MODE1)
  489. meram_write_reg(priv->base, MEVCR1, 1 << 29);
  490. pm_runtime_enable(&pdev->dev);
  491. dev_info(&pdev->dev, "sh_mobile_meram initialized.");
  492. return 0;
  493. err:
  494. sh_mobile_meram_remove(pdev);
  495. return error;
  496. }
  497. static int sh_mobile_meram_remove(struct platform_device *pdev)
  498. {
  499. struct sh_mobile_meram_priv *priv = platform_get_drvdata(pdev);
  500. pm_runtime_disable(&pdev->dev);
  501. if (priv->base)
  502. iounmap(priv->base);
  503. mutex_destroy(&priv->lock);
  504. kfree(priv);
  505. return 0;
  506. }
  507. static struct platform_driver sh_mobile_meram_driver = {
  508. .driver = {
  509. .name = "sh_mobile_meram",
  510. .owner = THIS_MODULE,
  511. .pm = &sh_mobile_meram_dev_pm_ops,
  512. },
  513. .probe = sh_mobile_meram_probe,
  514. .remove = sh_mobile_meram_remove,
  515. };
  516. static int __init sh_mobile_meram_init(void)
  517. {
  518. return platform_driver_register(&sh_mobile_meram_driver);
  519. }
  520. static void __exit sh_mobile_meram_exit(void)
  521. {
  522. platform_driver_unregister(&sh_mobile_meram_driver);
  523. }
  524. module_init(sh_mobile_meram_init);
  525. module_exit(sh_mobile_meram_exit);
  526. MODULE_DESCRIPTION("SuperH Mobile MERAM driver");
  527. MODULE_AUTHOR("Damian Hobson-Garcia / Takanari Hayama");
  528. MODULE_LICENSE("GPL v2");