From patchwork Mon May 6 09:33:48 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrzej Hajda X-Patchwork-Id: 18304 Received: from mail.tu-berlin.de ([130.149.7.33]) by www.linuxtv.org with esmtp (Exim 4.72) (envelope-from ) id 1UZHoi-0002ay-5X; Mon, 06 May 2013 11:34:44 +0200 X-tubIT-Incoming-IP: 209.132.180.67 Received: from vger.kernel.org ([209.132.180.67]) by mail.tu-berlin.de (exim-4.72/mailfrontend-8) with esmtp id 1UZHof-0007nC-ku; Mon, 06 May 2013 11:34:44 +0200 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753379Ab3EFJej (ORCPT + 1 other); Mon, 6 May 2013 05:34:39 -0400 Received: from mailout3.w1.samsung.com ([210.118.77.13]:29592 "EHLO mailout3.w1.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753337Ab3EFJei (ORCPT ); Mon, 6 May 2013 05:34:38 -0400 Received: from eucpsbgm1.samsung.com (unknown [203.254.199.244]) by mailout3.w1.samsung.com (Oracle Communications Messaging Server 7u4-24.01(7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTP id <0MMD00F1MEE39J80@mailout3.w1.samsung.com>; Mon, 06 May 2013 10:34:36 +0100 (BST) X-AuditID: cbfec7f4-b7faf6d00000189b-eb-5187792c2022 Received: from eusync2.samsung.com ( [203.254.199.212]) by eucpsbgm1.samsung.com (EUCPMTA) with SMTP id 03.BB.06299.C2977815; Mon, 06 May 2013 10:34:36 +0100 (BST) Received: from AMDC1061.digital.local ([106.116.147.88]) by eusync2.samsung.com (Oracle Communications Messaging Server 7u4-23.01 (7.0.4.23.0) 64bit (built Aug 10 2011)) with ESMTPA id <0MMD00DHYEL47T90@eusync2.samsung.com>; Mon, 06 May 2013 10:34:36 +0100 (BST) From: Andrzej Hajda To: linux-media@vger.kernel.org, linux-leds@vger.kernel.org, devicetree-discuss@lists.ozlabs.org Cc: Laurent Pinchart , Sylwester Nawrocki , Sakari Ailus , Kyungmin Park , hj210.choi@samsung.com, sw0312.kim@samsung.com, Bryan Wu , Richard Purdie , Andrzej Hajda Subject: [PATCH RFC 2/2] media: added max77693-led driver Date: Mon, 06 May 2013 11:33:48 +0200 Message-id: <1367832828-30771-3-git-send-email-a.hajda@samsung.com> X-Mailer: git-send-email 1.7.10.4 In-reply-to: <1367832828-30771-1-git-send-email-a.hajda@samsung.com> References: <1367832828-30771-1-git-send-email-a.hajda@samsung.com> X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFprFLMWRmVeSWpSXmKPExsVy+t/xK7o6le2BBrO+KVjcWneO1eLozolM FgdmP2S1eLzxGrPF2aY37BadE5ewW2x9s47RomfDVlaL3bueslocftPOanFm/0o2ixmTX7I5 8HjsnHWX3WN2x0xWj8NfF7J4nJ+xkNFjz/wfrB59W1YxenzeJBfAHsVlk5Kak1mWWqRvl8CV cfD2esaC3wsZK6a/7WNsYGxsZ+xi5OCQEDCReNZv0cXICWSKSVy4t56ti5GLQ0hgKaPExAnT WSGcPiaJzeuaWEGq2AQ0Jf5uvskGYosIJEmc+d4D1sEscIVJ4t2DD+wgCWEBS4mvf04wgtgs AqoS1zZvA2vmFXCWONj2iBVinaJE97MJbCBXcAq4SPz6UQ0SFgIqubBgH+sERt4FjAyrGEVT S5MLipPScw31ihNzi0vz0vWS83M3MULC88sOxsXHrA4xCnAwKvHwFj5pCxRiTSwrrsw9xCjB wawkwuuzFyjEm5JYWZValB9fVJqTWnyIkYmDU6qBsT7oip/M+WWich1JW1dvMGRzjZxc90jd UDQ0Nb7oa8G8qZ/W5z3KF/4+R73VL0Zc5d29l8opxd/f7+hMCHsmdO3H1InipvbHFTZ4X+hy sDYWrTj76pY+84vlam2+q6bnP5+zMqA4fNtKhckCTwoct4u/mmLZz3PgSYdwyMnMS+sUfYq5 Ph4yVGIpzkg01GIuKk4EAHt31cEtAgAA Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-PMX-Version: 6.0.0.2142326, Antispam-Engine: 2.7.2.2107409, Antispam-Data: 2013.5.6.92422 X-PMX-Spam: Gauge=IIIIIIIII, Probability=9%, Report=' HTML_NO_HTTP 0.1, MULTIPLE_RCPTS 0.1, HTML_00_10 0.05, BODY_SIZE_10000_PLUS 0, NO_URI_FOUND 0, __HAS_FROM 0, __HAS_HTML 0, __HAS_MSGID 0, __HAS_X_MAILER 0, __HAS_X_MAILING_LIST 0, __IN_REP_TO 0, __MIME_TEXT_ONLY 0, __MULTIPLE_RCPTS_CC_X2 0, __SANE_MSGID 0, __SUBJ_ALPHA_END 0, __TO_MALFORMED_2 0, __TO_NO_NAME 0' The patch adds led-flash support to Maxim max77693 chipset. Device is exposed to user space as a V4L2 subdevice. It can be accessed via V4L2 controls interface. Device supports up to two leds which can work in flash and torch mode. Leds can be triggered externally or by software. Device is also exposed via LED API using v4l2-leddev helper. Signed-off-by: Andrzej Hajda Reviewed-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park --- v3: - added v4l2-leddev support v2: - kzalloc replaced by devm_kzalloc - corrected cleanup code on probe fail - simplified clamp routine - cosmetic changes --- drivers/media/i2c/max77693-led.c | 650 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 650 insertions(+) create mode 100644 drivers/media/i2c/max77693-led.c diff --git a/drivers/media/i2c/max77693-led.c b/drivers/media/i2c/max77693-led.c new file mode 100644 index 0000000..a597611 --- /dev/null +++ b/drivers/media/i2c/max77693-led.c @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2013, Samsung Electronics Co., Ltd. + * Andrzej Hajda + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX77693_TORCH_IOUT_BITS 4 + +#define MAX77693_TORCH_NO_TIMER 0x40 +#define MAX77693_FLASH_TIMER_LEVEL 0x80 + +#define MAX77693_FLASH_EN_OFF 0 +#define MAX77693_FLASH_EN_FLASH 1 +#define MAX77693_FLASH_EN_TORCH 2 +#define MAX77693_FLASH_EN_ON 3 + +#define MAX77693_FLASH_EN1_SHIFT 6 +#define MAX77693_FLASH_EN2_SHIFT 4 +#define MAX77693_TORCH_EN1_SHIFT 2 +#define MAX77693_TORCH_EN2_SHIFT 0 + +#define MAX77693_FLASH_LOW_BATTERY_EN 0x80 + +#define MAX77693_FLASH_BOOST_FIXED 0x04 +#define MAX77693_FLASH_BOOST_LEDNUM_2 0x80 + +#define MAX77693_FLASH_TIMEOUT_MIN 62500 +#define MAX77693_FLASH_TIMEOUT_MAX 1000000 +#define MAX77693_FLASH_TIMEOUT_STEP 62500 + +#define MAX77693_TORCH_TIMEOUT_MIN 262000 +#define MAX77693_TORCH_TIMEOUT_MAX 15728000 + +#define MAX77693_FLASH_IOUT_MIN 15625 +#define MAX77693_FLASH_IOUT_MAX_1LED 1000000 +#define MAX77693_FLASH_IOUT_MAX_2LEDS 625000 +#define MAX77693_FLASH_IOUT_STEP 15625 + +#define MAX77693_TORCH_IOUT_MIN 15625 +#define MAX77693_TORCH_IOUT_MAX 250000 +#define MAX77693_TORCH_IOUT_STEP 15625 + +#define MAX77693_FLASH_VSYS_MIN 2400 +#define MAX77693_FLASH_VSYS_MAX 3400 +#define MAX77693_FLASH_VSYS_STEP 33 + +#define MAX77693_FLASH_VOUT_MIN 3300 +#define MAX77693_FLASH_VOUT_MAX 5500 +#define MAX77693_FLASH_VOUT_STEP 25 +#define MAX77693_FLASH_VOUT_RMIN 0x0c + +#define MAX77693_LED_STATUS_FLASH_ON (1 << 3) +#define MAX77693_LED_STATUS_TORCH_ON (1 << 2) + +enum { + FLASH1, + FLASH2, + TORCH1, + TORCH2 +}; + +enum { + FLASH, + TORCH +}; + +struct max77693_led { + struct regmap *regmap; + struct v4l2_subdev subdev; + struct platform_device *pdev; + struct max77693_led_platform_data *pdata; + struct v4l2_leddev leddev; + + struct v4l2_ctrl_handler hdl; + struct { + struct v4l2_ctrl *led_mode; + struct v4l2_ctrl *source; + } ctrl; +}; + +static u8 max77693_led_iout_to_reg(u32 ua) +{ + if (ua < MAX77693_FLASH_IOUT_MIN) + ua = MAX77693_FLASH_IOUT_MIN; + return (ua - MAX77693_FLASH_IOUT_MIN) / MAX77693_FLASH_IOUT_STEP; +} + +static u8 max77693_led_timeout_to_reg(u32 us) +{ + return (us - MAX77693_FLASH_TIMEOUT_MIN) / MAX77693_FLASH_TIMEOUT_STEP; +} + +static const u32 max77693_torch_timeouts[] = { + 262000, 524000, 786000, 1048000, + 1572000, 2096000, 2620000, 3144000, + 4193000, 5242000, 6291000, 7340000, + 9437000, 11534000, 13631000, 1572800 +}; + +static u8 max77693_torch_timeout_to_reg(u32 us) +{ + int i, b = 0, e = ARRAY_SIZE(max77693_torch_timeouts); + + while (e - b > 1) { + i = b + (e - b) / 2; + if (us < max77693_torch_timeouts[i]) + e = i; + else + b = i; + } + return b; +} + +static u32 max77693_torch_timeout_from_reg(u8 reg) +{ + return max77693_torch_timeouts[reg]; +} + +static u8 max77693_led_vsys_to_reg(u32 mv) +{ + return ((mv - MAX77693_FLASH_VSYS_MIN) / MAX77693_FLASH_VSYS_STEP) << 2; +} + +static u8 max77693_led_vout_to_reg(u32 mv) +{ + return (mv - MAX77693_FLASH_VOUT_MIN) / MAX77693_FLASH_VOUT_STEP + + MAX77693_FLASH_VOUT_RMIN; +} + +enum max77693_led_mode { + MODE_OFF, + MODE_FLASH, + MODE_TORCH, + MODE_EXTERNAL +}; + +static int max77693_led_set_mode(struct max77693_led *flash, + enum max77693_led_mode mode) +{ + struct max77693_led_platform_data *p = flash->pdata; + struct regmap *rmap = flash->regmap; + int ret, v = 0; + + switch (mode) { + case MODE_OFF: + break; + case MODE_FLASH: + if (p->trigger[FLASH1] & MAX77693_LED_TRIG_SOFT) + v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN1_SHIFT; + if (p->trigger[FLASH2] & MAX77693_LED_TRIG_SOFT) + v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN2_SHIFT; + break; + case MODE_TORCH: + if (p->trigger[TORCH1] & MAX77693_LED_TRIG_SOFT) + v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN1_SHIFT; + if (p->trigger[TORCH2] & MAX77693_LED_TRIG_SOFT) + v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN2_SHIFT; + break; + case MODE_EXTERNAL: + v |= (p->trigger[FLASH1] & MAX77693_LED_TRIG_EXT) << + MAX77693_FLASH_EN1_SHIFT; + v |= (p->trigger[FLASH2] & MAX77693_LED_TRIG_EXT) << + MAX77693_FLASH_EN2_SHIFT; + v |= (p->trigger[TORCH1] & MAX77693_LED_TRIG_EXT) << + MAX77693_TORCH_EN1_SHIFT; + v |= (p->trigger[TORCH2] & MAX77693_LED_TRIG_EXT) << + MAX77693_TORCH_EN2_SHIFT; + break; + } + if (v != 0) + max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, 0); + ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, v); + + return ret; +} + +static int max77693_led_setup(struct max77693_led *flash) +{ + struct max77693_led_platform_data *p = flash->pdata; + struct regmap *rmap = flash->regmap; + int ret; + u8 v; + + v = max77693_led_iout_to_reg(p->iout[FLASH1]); + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH1, v); + if (ret < 0) + return ret; + + v = max77693_led_iout_to_reg(p->iout[FLASH2]); + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH2, v); + if (ret < 0) + return ret; + + v = max77693_led_iout_to_reg(p->iout[TORCH1]); + v |= max77693_led_iout_to_reg(p->iout[TORCH2]) << + MAX77693_TORCH_IOUT_BITS; + ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCH, v); + if (ret < 0) + return ret; + + if (p->timeout[TORCH] > 0) + v = max77693_torch_timeout_to_reg(p->timeout[TORCH]); + else + v = MAX77693_TORCH_NO_TIMER; + if (p->trigger_type[TORCH] == MAX77693_LED_TRIG_TYPE_LEVEL) + v |= MAX77693_FLASH_TIMER_LEVEL; + ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCHTIMER, v); + if (ret < 0) + return ret; + + v = max77693_led_timeout_to_reg(p->timeout[FLASH]); + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL) + v |= MAX77693_FLASH_TIMER_LEVEL; + ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v); + if (ret < 0) + return ret; + + if (p->low_vsys > 0) + v = max77693_led_vsys_to_reg(p->low_vsys) | + MAX77693_FLASH_LOW_BATTERY_EN; + else + v = 0; + + ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH1, v); + if (ret < 0) + return ret; + ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH2, 0); + if (ret < 0) + return ret; + + if (p->boost_mode[FLASH1] == MAX77693_LED_BOOST_FIXED || + p->boost_mode[FLASH2] == MAX77693_LED_BOOST_FIXED) + v = MAX77693_FLASH_BOOST_FIXED; + else + v = p->boost_mode[FLASH1] | (p->boost_mode[FLASH2] << 1); + if (p->boost_mode[FLASH1] && p->boost_mode[FLASH2]) + v |= MAX77693_FLASH_BOOST_LEDNUM_2; + ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_CNTL, v); + if (ret < 0) + return ret; + + v = max77693_led_vout_to_reg(p->boost_vout); + ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_FLASH1, v); + if (ret < 0) + return ret; + + ret = max77693_led_set_mode(flash, MODE_OFF); + + return ret; +} + +static struct max77693_led *ctrl_to_flash(struct v4l2_ctrl *c) +{ + return container_of(c->handler, struct max77693_led, hdl); +} + +static struct max77693_led *subdev_to_flash(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max77693_led, subdev); +} + +static int max77693_led_get_ctrl(struct v4l2_ctrl *c) +{ + struct max77693_led *flash = ctrl_to_flash(c); + struct regmap *rmap = flash->regmap; + int ret; + u8 v; + + switch (c->id) { + case V4L2_CID_FLASH_STROBE_STATUS: + ret = max77693_read_reg(rmap, MAX77693_LED_REG_FLASH_INT_STATUS, + &v); + c->val = !!(v & MAX77693_LED_STATUS_FLASH_ON); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/* split composite current @i into two @iout according to @imax weights */ +static void max77693_led_calc_iout(u32 iout[2], u32 i, u32 imax[2]) +{ + u64 t = i; + + t *= imax[1]; + do_div(t, imax[0] + imax[1]); + + iout[1] = (u32)t / MAX77693_FLASH_IOUT_STEP * MAX77693_FLASH_IOUT_STEP; + iout[0] = i - iout[1]; +} + +static int max77693_led_set_ctrl(struct v4l2_ctrl *c) +{ + struct max77693_led *flash = ctrl_to_flash(c); + struct max77693_led_platform_data *p = flash->pdata; + struct regmap *rmap = flash->regmap; + int v, ret = 0; + u32 iout[2]; + + switch (c->id) { + case V4L2_CID_FLASH_LED_MODE: + switch (c->val) { + case V4L2_FLASH_LED_MODE_NONE: + ret = max77693_led_set_mode(flash, MODE_OFF); + break; + case V4L2_FLASH_LED_MODE_FLASH: + if (flash->ctrl.source->val == + V4L2_FLASH_STROBE_SOURCE_EXTERNAL) + ret = max77693_led_set_mode(flash, + MODE_EXTERNAL); + else + ret = max77693_led_set_mode(flash, MODE_OFF); + break; + case V4L2_FLASH_LED_MODE_TORCH: + ret = max77693_led_set_mode(flash, MODE_TORCH); + break; + } + break; + case V4L2_CID_FLASH_STROBE_SOURCE: + if (c->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL) + ret = max77693_led_set_mode(flash, MODE_EXTERNAL); + else + ret = max77693_led_set_mode(flash, MODE_OFF); + break; + case V4L2_CID_FLASH_STROBE: + if (flash->ctrl.led_mode->val != V4L2_FLASH_LED_MODE_FLASH || + flash->ctrl.source->val != V4L2_FLASH_STROBE_SOURCE_SOFTWARE) + return -EINVAL; + ret = max77693_led_set_mode(flash, MODE_FLASH); + break; + case V4L2_CID_FLASH_STROBE_STOP: + ret = max77693_led_set_mode(flash, MODE_OFF); + break; + case V4L2_CID_FLASH_TIMEOUT: + v = max77693_led_timeout_to_reg(c->val); + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL) + v |= MAX77693_FLASH_TIMER_LEVEL; + ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v); + break; + case V4L2_CID_FLASH_INTENSITY: + max77693_led_calc_iout(iout, 1000 * c->val, &p->iout[FLASH1]); + v = max77693_led_iout_to_reg(iout[0]); + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH1, v); + if (ret < 0) + break; + v = max77693_led_iout_to_reg(iout[1]); + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH2, v); + break; + case V4L2_CID_FLASH_TORCH_INTENSITY: + max77693_led_calc_iout(iout, 1000 * c->val, &p->iout[TORCH1]); + v = max77693_led_iout_to_reg(iout[0]); + v |= max77693_led_iout_to_reg(iout[1]) << + MAX77693_TORCH_IOUT_BITS; + ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCH, v); + } + + return ret; +} + +static const struct v4l2_ctrl_ops max77693_led_ctrl_ops = { + .g_volatile_ctrl = max77693_led_get_ctrl, + .s_ctrl = max77693_led_set_ctrl, +}; + +static int max77693_init_controls(struct max77693_led *flash) +{ + struct max77693_led_platform_data *p = flash->pdata; + struct v4l2_ctrl *ctrl; + int mask, max; + + v4l2_ctrl_handler_init(&flash->hdl, 8); + + mask = 1 << V4L2_FLASH_LED_MODE_NONE; + max = V4L2_FLASH_LED_MODE_NONE; + + if (p->trigger[FLASH1] | p->trigger[FLASH2] | + ((p->trigger[TORCH1] | p->trigger[TORCH2]) & + ~MAX77693_LED_TRIG_SOFT)) { + mask |= 1 << V4L2_FLASH_LED_MODE_FLASH; + max = V4L2_FLASH_LED_MODE_FLASH; + } + if (p->trigger[TORCH1] | p->trigger[TORCH2]) { + mask |= 1 << V4L2_FLASH_LED_MODE_TORCH; + max = V4L2_FLASH_LED_MODE_TORCH; + } + + flash->ctrl.led_mode = v4l2_ctrl_new_std_menu(&flash->hdl, + &max77693_led_ctrl_ops, V4L2_CID_FLASH_LED_MODE, + max, ~mask, V4L2_FLASH_LED_MODE_NONE); + + mask = 1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE; + max = V4L2_FLASH_STROBE_SOURCE_SOFTWARE; + + if ((p->trigger[FLASH1] | p->trigger[FLASH2] | p->trigger[TORCH1] | + p->trigger[TORCH2]) & ~MAX77693_LED_TRIG_SOFT) { + mask |= 1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL; + max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL; + } + + flash->ctrl.source = v4l2_ctrl_new_std_menu(&flash->hdl, + &max77693_led_ctrl_ops, V4L2_CID_FLASH_STROBE_SOURCE, + max, ~mask, V4L2_FLASH_STROBE_SOURCE_SOFTWARE); + + + v4l2_ctrl_new_std(&flash->hdl, &max77693_led_ctrl_ops, + V4L2_CID_FLASH_STROBE, 0, 0, 0, 0); + + + v4l2_ctrl_new_std(&flash->hdl, &max77693_led_ctrl_ops, + V4L2_CID_FLASH_STROBE_STOP, 0, 0, 0, 0); + + + ctrl = v4l2_ctrl_new_std(&flash->hdl, &max77693_led_ctrl_ops, + V4L2_CID_FLASH_STROBE_STATUS, 0, 1, 1, 1); + if (ctrl != NULL) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_new_std(&flash->hdl, &max77693_led_ctrl_ops, + V4L2_CID_FLASH_TIMEOUT, MAX77693_FLASH_TIMEOUT_MIN, + MAX77693_FLASH_TIMEOUT_MAX, + MAX77693_FLASH_TIMEOUT_STEP, p->timeout[FLASH]); + + max = (p->iout[FLASH1] + p->iout[FLASH2]) / 1000; + v4l2_ctrl_new_std(&flash->hdl, &max77693_led_ctrl_ops, + V4L2_CID_FLASH_INTENSITY, + MAX77693_FLASH_IOUT_MIN / 1000, max, 1, max); + + max = (p->iout[TORCH1] + p->iout[TORCH2]) / 1000; + v4l2_ctrl_new_std(&flash->hdl, &max77693_led_ctrl_ops, + V4L2_CID_FLASH_TORCH_INTENSITY, + MAX77693_TORCH_IOUT_MIN / 1000, max, 1, max); + + flash->subdev.ctrl_handler = &flash->hdl; + + return flash->hdl.error; +} + +static void max77693_led_parse_dt(struct max77693_led_platform_data *p, + struct device_node *node) +{ + of_property_read_u32_array(node, "maxim,iout", p->iout, 4); + of_property_read_u32_array(node, "maxim,trigger", p->trigger, 4); + of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type, + 2); + of_property_read_u32_array(node, "maxim,timeout", p->timeout, 2); + of_property_read_u32_array(node, "maxim,boost-mode", p->boost_mode, 2); + of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout); + of_property_read_u32(node, "maxim,low-vsys", &p->low_vsys); +} + +static void clamp_align(u32 *v, u32 min, u32 max, u32 step) +{ + *v = clamp_val(*v, min, max); + if (step > 1) + *v = (*v - min) / step * step + min; +} + +static void max77693_led_validate_platform_data( + struct max77693_led_platform_data *p) +{ + u32 max; + int i; + + for (i = 0; i < 2; ++i) + clamp_align(&p->boost_mode[i], MAX77693_LED_BOOST_NONE, + MAX77693_LED_BOOST_FIXED, 1); + /* boost, if enabled, should be the same on both leds */ + if (p->boost_mode[0] != MAX77693_LED_BOOST_NONE && + p->boost_mode[1] != MAX77693_LED_BOOST_NONE) + p->boost_mode[1] = p->boost_mode[0]; + + max = (p->boost_mode[FLASH1] && p->boost_mode[FLASH2]) ? + MAX77693_FLASH_IOUT_MAX_2LEDS : MAX77693_FLASH_IOUT_MAX_1LED; + + clamp_align(&p->iout[FLASH1], MAX77693_FLASH_IOUT_MIN, + max, MAX77693_FLASH_IOUT_STEP); + clamp_align(&p->iout[FLASH2], MAX77693_FLASH_IOUT_MIN, + max, MAX77693_FLASH_IOUT_STEP); + clamp_align(&p->iout[TORCH1], MAX77693_TORCH_IOUT_MIN, + MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP); + clamp_align(&p->iout[TORCH2], MAX77693_TORCH_IOUT_MIN, + MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP); + + for (i = 0; i < 4; ++i) + clamp_align(&p->trigger[i], 0, 7, 1); + for (i = 0; i < 2; ++i) + clamp_align(&p->trigger_type[i], MAX77693_LED_TRIG_TYPE_EDGE, + MAX77693_LED_TRIG_TYPE_LEVEL, 1); + + clamp_align(&p->timeout[FLASH], MAX77693_FLASH_TIMEOUT_MIN, + MAX77693_FLASH_TIMEOUT_MAX, MAX77693_FLASH_TIMEOUT_STEP); + + if (p->timeout[TORCH]) { + clamp_align(&p->timeout[TORCH], MAX77693_TORCH_TIMEOUT_MIN, + MAX77693_TORCH_TIMEOUT_MAX, 1); + p->timeout[TORCH] = max77693_torch_timeout_from_reg( + max77693_torch_timeout_to_reg(p->timeout[TORCH])); + } + + clamp_align(&p->boost_vout, MAX77693_FLASH_VOUT_MIN, + MAX77693_FLASH_VOUT_MAX, MAX77693_FLASH_VOUT_STEP); + + if (p->low_vsys) { + clamp_align(&p->low_vsys, MAX77693_FLASH_VSYS_MIN, + MAX77693_FLASH_VSYS_MAX, MAX77693_FLASH_VSYS_STEP); + } +} + +static int max77693_led_get_platform_data(struct max77693_led *flash) +{ + struct max77693_led_platform_data *p; + struct device *dev = &flash->pdev->dev; + + if (dev->of_node) { + p = devm_kzalloc(dev, sizeof(*flash->pdata), GFP_KERNEL); + if (!p) + return -ENOMEM; + max77693_led_parse_dt(p, dev->of_node); + } else { + p = dev_get_platdata(dev); + if (!p) + return -ENODEV; + } + flash->pdata = p; + + max77693_led_validate_platform_data(p); + + dev_info(dev, "Iout[uA]=%d,%d,%d,%d; Triggers=%d,%d,%d,%d;" + "TriggerTypes=%d,%d;Timeouts[us]=%d,%d;BM=%d,%d;" + "Vout[mV]=%d;LowVsys[mV]=%d\n", + p->iout[0], p->iout[1], p->iout[2], p->iout[3], + p->trigger[0], p->trigger[1], p->trigger[2], p->trigger[3], + p->trigger_type[0], p->trigger_type[1], + p->timeout[0], p->timeout[1], + p->boost_mode[0], p->boost_mode[1], + p->boost_vout, p->low_vsys); + + return 0; +} + +/* v4l2_subdev_init requires this structure */ +static struct v4l2_subdev_ops max77693_led_subdev_ops = { +}; + +static int max77693_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct max77693_dev *iodev = dev_get_drvdata(dev->parent); + struct max77693_led *flash; + struct v4l2_subdev *sd; + int ret; + + flash = devm_kzalloc(dev, sizeof(*flash), GFP_KERNEL); + if (!flash) + return -ENOMEM; + + flash->pdev = pdev; + flash->regmap = iodev->regmap; + ret = max77693_led_get_platform_data(flash); + if (ret < 0) + return ret; + + sd = &flash->subdev; + v4l2_subdev_init(sd, &max77693_led_subdev_ops); + sd->owner = pdev->dev.driver->owner; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + strlcpy(sd->name, "max77693-led", sizeof(sd->name)); + platform_set_drvdata(pdev, sd); + + ret = max77693_init_controls(flash); + if (ret < 0) + goto err; + + ret = media_entity_init(&sd->entity, 0, NULL, 0); + if (ret < 0) + goto err; + + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_FLASH; + + ret = max77693_led_setup(flash); + if (!ret) { + flash->leddev.sd = sd; + ret = v4l2_leddev_register(dev, &flash->leddev); + } + if (!ret) + return 0; + + media_entity_cleanup(&sd->entity); +err: + v4l2_ctrl_handler_free(&flash->hdl); + return ret; +} + +static int max77693_led_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct max77693_led *flash = subdev_to_flash(sd); + + v4l2_leddev_unregister(&flash->leddev); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&flash->hdl); + + return 0; +} + +static struct of_device_id max77693_led_dt_match[] = { + {.compatible = "maxim,max77693-led"}, + {}, +}; + +static struct platform_driver max77693_led_driver = { + .probe = max77693_led_probe, + .remove = max77693_led_remove, + .driver = { + .name = "max77693-led", + .owner = THIS_MODULE, + .of_match_table = max77693_led_dt_match, + }, +}; + +module_platform_driver(max77693_led_driver); + +MODULE_AUTHOR("Andrzej Hajda "); +MODULE_DESCRIPTION("Maxim MAX77693 led flash driver"); +MODULE_LICENSE("GPL");